Published
Edited
Nov 11, 2021
1 star
Insert cell
# Math puzzle: Frog jumping in a circle
Insert cell
Cards numbered 1 to 12 are arranged in a circle. A frog starts on card 1, then jumps to the card 1 place clockwise around the circle. Then, if the frog is on card 5, for example, he jumps to the card 5 places clockwise around the circle. If the frog is on card 9, for example, he jumps to the card 9 places clockwise around the circle, and so on. Find an arrangement of cards that allows the frog to visit every card.
Insert cell
diagram = {

// should clear timeout on change to card or number of cards
let div = d3.create("div")
.style("height","450px")
.style("text-align","center")
div.append("label")
.attr("for","num-cards")
.text("Number of cards: ")
.style("margin-bottom","20px")
let selectCards = div
.append("input")
.attr("id","num-cards")
.attr("name","num-cards")
.attr("type","number")
.attr("min",1)
.attr("max",20)
.attr("value",12)
.style("width","50px")

selectCards.on("change",(e)=>{
d3.selectAll(".card").remove()
placeCards(e.target.value)
});

let circle = div.append("div")
.style("border", "1px solid grey")
.style("border-radius","50%")
.style("width","300px")
.style("height","300px")
.style("position","relative")
.style("top","70px")
.style("left","50px")
.style("margin","auto")

let frogImage = circle.append("img")
.attr("id","frog-image")
.attr("src", await FileAttachment("frog254207.png").url())
.attr("width","50px")
.attr("width","50px")
.style("position","absolute")
.style("bottom","320px")
.style("left","130px")
.style("z-index","1")

let feedback = circle.append("div")
.attr("id","feedback")
.style("width","200px")
.style("position","absolute")
.style("text-align", "center")
.style("top","100px")
.style("left","50px")
.style("display", "none")
let frogPosition = 0;
let visitedCards = [];
let placeCards = (num) => {
frogPosition = 0;
frogImage.transition()
.duration(500)
.style("bottom", "320px")
.style("left", "130px")
visitedCards = [];
feedback.style("display","none");
for(let i=0; i<num; i++){
let card = circle.append("input")
.classed("card",true)
.attr("id",`card-${i}`)
.attr("type","number")
.attr("min",2)
.attr("max",num)
.style("font-size","30px")
.style("text-align","center")
.style("width","50px")
.style("height","40px")
.style("position","absolute")
.style("bottom",`${150 - 20 + 150* Math.cos(2*i*Math.PI/num)}px`)
.style("left",`${150 - 25 + 150* Math.sin(2*i*Math.PI/num)}px`)
.style("border","2px solid #5C4033")
.style("border-radius","5px")
.style("box-shadow","3px 3px 2px grey")
.style("background-color","#f1d592")
if(i===0){
card.attr("readonly", "true")
.attr("value",1)
}
visitedCards.push(0);

card.on("input",(e)=>{
d3.selectAll(".card").style("background-color","#f1d592");
feedback.style("display","none");
frogImage.transition()
.duration(500)
.style("bottom", "320px")
.style("left", "130px")
let checkCards = document.querySelectorAll(".card");
let startFrog = true;
for(let i=0; i<checkCards.length; i++){
if(!checkCards[i].value){
startFrog = false;
d3.select("#goButton").transition().style("opacity",0);
d3.select("#goButton").style("display","none");
}
}
if(startFrog){
d3.select("#goButton").style("display","block")
.transition().style("opacity",1);
}
});
}
}
placeCards(12);
let goButton = circle.append("button")
.attr("id","goButton")
.style("font-size","20px")
.style("width","60px")
.style("height","30px")
.style("position","absolute")
.style("top","135px")
.style("left","120px")
.style("display","none")
.style("opacity",0)
.text("Go!")
.attr("value","go")

goButton.on("click",(e)=>{
d3.select(e.target).transition().style("opacity",0);
d3.select(e.target).style("display","none");
let numCards = parseInt(document.querySelector("#num-cards").value);
d3.selectAll(".card").style("background-color","#f1d592");
feedback.style("display","none");
visitedCards = visitedCards.map(d=>0);
frogPosition = 0;
//check each number has been used exactly once
let allCards = document.querySelectorAll(".card");
let usedValues = [];
for(let i=0; i<numCards; i++){
usedValues.push(+allCards[i].value);
}
usedValues.sort();
for(let i=0; i<numCards; i++){
if(usedValues[i] === usedValues[i+1]){
feedback.text("Number "+usedValues[i]+" has been used twice");
feedback.style("display","block");
return false;
}
if(usedValues[i] > numCards){
feedback.text("Only use numbers up to "+numCards);
feedback.style("display","block");
return false;
}
}
visitedCards[0] = 1;
document.querySelector(`#card-${frogPosition}`).style.backgroundColor = "lightgreen";
for(let i=0; i<numCards + 1; i++){
setTimeout(()=>{
if(i > 0 && visitedCards[frogPosition] === 1){ //frog is back to a card he has visited before
let numVisited = visitedCards.reduce((a, b) => a + b, 0);
if(numVisited === numCards){
feedback.text("You did it! 👏👏👏");
feedback.style("display","block");
}
else{
feedback.text(`Froggie visited ${numVisited} cards. Keep trying!`)
feedback.style("display","block")
}
return false;
}
visitedCards[frogPosition] = 1;

document.querySelector(`#card-${frogPosition}`).style.backgroundColor = "lightgreen";
let cardValue = parseInt(document.querySelector(`#card-${frogPosition}`).value)
frogPosition = (frogPosition + cardValue) % numCards;
frogImage.transition()
.duration(500)
.style("bottom", parseInt(document.querySelector(`#card-${frogPosition}`).style.bottom) + 40 + "px")
.style("left", parseInt(document.querySelector(`#card-${frogPosition}`).style.left) + 5 + "px")
},i * 1500)
}
});
return div.node();
}
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more