Skip to content

Instantly share code, notes, and snippets.

@EE2dev
Last active September 30, 2016 21:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save EE2dev/26a29797725abc3bc4411e879b20f4bb to your computer and use it in GitHub Desktop.
Save EE2dev/26a29797725abc3bc4411e879b20f4bb to your computer and use it in GitHub Desktop.
birthday guesser

Here is the puzzle, I address with this block:

Can you guess a birthday (just 1 out of 366 days, i.e. no year) under the following conditions:

  • just ask 9 yes/no questions
  • all questions must be independent, i.e. one question must not depend on an answer of a previous question
  • answering the questions should be straight forward, i.e. any 6-year old kid can answer them
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
rect.solution {
fill-opacity: 0.2;
stroke: black;
}
text.answer, text.solution{
text-anchor: middle;
}
text.mydays{
font-size: 16px;
text-anchor: middle;
}
div.center {
text-align: center;
}
svg {
margin-left: auto;
margin-right: auto;
display: block;
}
div, h1, h5, h2, text {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.background {
fill: #eee;
}
line {
stroke: #fff;
}
.day {
fill: #fff;
stroke: #ccc;
}
.month {
fill: none;
stroke: #fff;
stroke-width: 4px;
}
.year-title {
font-size: 1.5em;
}
.color {
fill: green;
fill-opacity: 0.4;
}
.nocolor {
fill: #fff
}
body {
font: 1.1em sans-serif;
}
</style>
</head>
<body>
<script>
var maxNumber = 367;
var currentCard = 0;
var solution = 0;
var width = 1000,
height = 900,
z = 20,
x = width / z,
y = height / z;
var solutionDays = d3.timeDays(new Date(2016, 0, 1), new Date(2017, 0, 1));
var solutionFormat = d3.timeFormat("%B %e");
var myDaysFormat = d3.timeFormat("%e");
var shuffleSolution = true;
var shuffleMap, unShuffleMap;
console.log(getMagicNumbers(maxNumber));
var myDiv = d3.select("body").append("div").attr("class", "center");
myDiv.append("h1").attr("class", "task").text("I will try to guess your birthday after asking 9 yes/no questions!");
myDiv.append("h5").attr("class", "task").text("Don't say your birthday out loud!");
myDiv.append("h3").attr("class", "task").text("Is your birthday marked on the following calendar?");
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
var myCards = getMagicNumbers(maxNumber);
getShuffleMap(maxNumber);
drawCards(svg);
var answers = svg.selectAll("g.answer")
.data(["yes","no"])
.enter()
.append("g")
.attr("class", "answer")
.attr("transform", function (d, i) { return "translate(" + (380 + i * 200) + ", 60)";});;
answers.append("text")
.text(function (d) { return d;})
.attr("y", "0.3em")
.attr("class", "answer")
.style("font-size", "24px");
answers.append("circle")
.attr("r", 50)
.style("stroke", function(d, i) {return (i === 0) ? "green" : "red"})
.style("fill", "lightgrey")
.style("fill-opacity", 0.4)
.on("mouseover", function(d, i) {
var col = (i === 0) ? "green" : "red";
d3.select(this).style("fill", col).style("stroke-width", 3);})
.on("mouseout", function() {
d3.select(this).style("fill", "lightgrey").style("stroke-width", 1);})
.on("click", nextCard);
d3.select(".card.c" + currentCard)
.transition()
.attr("transform", "translate(170, 110) scale(0.8)");
function drawCards(_selection) {
// calendar implementation from http://kathyz.github.io/d3-calendar/
var width = 960,
height = 750,
cellSize = 25; // cell size
var no_months_in_a_row = Math.floor(width / (cellSize * 7 + 50));
var shift_up = cellSize * 3;
var day = d3.timeFormat("%w"), // day of the week
day_of_month = d3.timeFormat("%e") // day of the month
day_of_year = d3.timeFormat("%j")
week = d3.timeFormat("%U"), // week number of the year
month = d3.timeFormat("%m"), // month number
year = d3.timeFormat("%Y"),
percent = d3.format(".1%"),
format = d3.timeFormat("%Y-%m-%d");
var color = d3.scaleQuantize()
.domain([-.05, .05])
.range(d3.range(11).map(function(d) { return "q" + d + "-11"; }));
var selection = _selection.selectAll("g")
.data(myCards)
.enter()
.append("g")
.attr("class", function (d,i) { return "card c" + i;})
.attr("transform", function (d, i) { return "translate(" + (68 + (i - 1) * 110) + ", 660) scale(0.1)";});
function daysData(d, i) {
var allDays = d3.timeDays(new Date(2016, 0, 1), new Date(2017, 0, 1));
allDays.forEach(function(element, index, array) {
array[index] = { value: element, card: i};
});
return allDays;
};
var rect = selection.selectAll(".day")
.data(daysData)
.enter().append("rect")
//.attr("class", function(d, i) { return (myCards[d.card].indexOf(i) != -1) ? "day color" : "day nocolor";})
.attr("class", function(d, i) { return (myCards[d.card].indexOf(shuffleMap.get(i)) != -1) ? "day color" : "day nocolor";})
.attr("width", cellSize)
.attr("height", cellSize)
.attr("x", function(data) {
var d = data.value;
var month_padding = 1.2 * cellSize*7 * ((month(d)-1) % (no_months_in_a_row));
return day(d) * cellSize + month_padding;
})
.attr("y", function(data) {
var d = data.value;
var week_diff = week(d) - week(new Date(year(d), month(d)-1, 1) );
var row_level = Math.ceil(month(d) / (no_months_in_a_row));
return (week_diff*cellSize) + row_level*cellSize*8 - cellSize/2 - shift_up;
})
.datum(format);
var myDays = selection.selectAll("text.mydays")
.data(d3.timeDays(new Date(2016, 0, 1), new Date(2017, 0, 1)))
.enter().append("text")
.attr("class", "mydays")
.text(function(d) {return myDaysFormat(d);})
.attr("x", function(d) {
var month_padding = 1.2 * cellSize*7 * ((month(d)-1) % (no_months_in_a_row));
return day(d) * cellSize + month_padding + cellSize / 2;
})
.attr("y", function(d) {
var week_diff = week(d) - week(new Date(year(d), month(d)-1, 1) );
var row_level = Math.ceil(month(d) / (no_months_in_a_row));
return (week_diff*cellSize) + row_level*cellSize*8 - cellSize/2 - shift_up + cellSize - 6;
})
var month_titles = selection.selectAll(".month-title")
.data(d3.timeMonths(new Date(2016, 0, 1), new Date(2017, 0, 1)))
.enter().append("text")
.text(monthTitle)
.attr("x", function(d, i) {
var month_padding = 1.2 * cellSize*7* ((month(d)-1) % (no_months_in_a_row));
return month_padding;
})
.attr("y", function(d, i) {
var week_diff = week(d) - week(new Date(year(d), month(d)-1, 1) );
var row_level = Math.ceil(month(d) / (no_months_in_a_row));
return (week_diff*cellSize) + row_level*cellSize*8 - cellSize - shift_up;
})
.attr("class", "month-title")
.attr("d", monthTitle);
}
function dayTitle (t0) {
return t0.toString().split(" ")[2];
}
function monthTitle (t0) {
// return t0.toLocaleString("en-us", { month: "long" }); // doesn't work in Safari
var monthFormat = d3.timeFormat("%B");
return monthFormat(t0);
}
function getShuffleMap(upperBound){
var shuffledArray = d3.range(1, upperBound, 1);
if (shuffleSolution) {d3.shuffle(shuffledArray);}
shuffleMap = d3.map();
unShuffleMap = d3.map();
shuffledArray.forEach(function (element, index) {
shuffleMap.set(index, element);
unShuffleMap.set(element, index);
});
}
function getMagicNumbers(upperBound) {
var cards = [];
var firstNumber;
var max = Math.log(upperBound) * Math.LOG2E; // polyfill for IE where Math.log2 doesn't work
// for (i = 0; i < Math.log2(upperBound); i++) {
for (i = 0; i < max; i++) {
cards[i] = [];
firstNumber = Math.pow(2, i);
for (number = 1; number <= upperBound; number++) {
if (((Math.floor(number /firstNumber)) % 2) != 0) {
cards[i].push(number);
}
}
}
// d3.shuffle(cards);
return cards;
}
function nextCard(d, i) {
d3.selectAll("circle").style("fill", "lightgrey").style("stroke-width", 1);
if (d === "yes") {solution = solution + myCards[currentCard][0];}
// remove card
d3.select(".card.c" + currentCard)
.transition()
.attr("transform", "translate(400, 400) scale(0)");
currentCard++;
if (currentCard < myCards.length) {
// display next card
console.log("next");
d3.select(".card.c" + currentCard)
.transition()
.attr("transform", "translate(170, 110) scale(0.8)");
}
else {
d3.selectAll("circle").remove();
d3.selectAll("text").remove();
d3.selectAll(".task").remove();
myDiv.append("h1").text("Please focus now!");
solution = unShuffleMap.get(solution);
var solutionOutput = (solution <= 366) ? solutionFormat(solutionDays[solution]) : "That was too hard!?"
console.log("solution: " + solution);
console.log("solution date: " + solutionOutput);
svg.append("rect")
.attr("class", "solution")
.attr("x", 50)
.attr("y", 50)
.attr("width", 900)
.attr("height", 250)
.style("fill", "lightblue");
svg.append("text")
.attr("class", "solution")
.text(solutionOutput)
.attr("x", 500)
.attr("y", 200)
.style("font-size", "100px")
.style("stroke", "black")
.style("opacity", 0)
.transition().duration(12000)
.style("opacity", 1);
}
}
// Production steps of ECMA-262, Edition 5, 15.4.4.14
// Reference: http://es5.github.io/#x15.4.4.14
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(searchElement, fromIndex) {
var k;
// 1. Let o be the result of calling ToObject passing
// the this value as the argument.
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
var o = Object(this);
// 2. Let lenValue be the result of calling the Get
// internal method of o with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = o.length >>> 0;
// 4. If len is 0, return -1.
if (len === 0) {
return -1;
}
// 5. If argument fromIndex was passed let n be
// ToInteger(fromIndex); else let n be 0.
var n = +fromIndex || 0;
if (Math.abs(n) === Infinity) {
n = 0;
}
// 6. If n >= len, return -1.
if (n >= len) {
return -1;
}
// 7. If n >= 0, then Let k be n.
// 8. Else, n<0, Let k be len - abs(n).
// If k is less than 0, then let k be 0.
k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
// 9. Repeat, while k < len
while (k < len) {
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the
// HasProperty internal method of o with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
// i. Let elementK be the result of calling the Get
// internal method of o with the argument ToString(k).
// ii. Let same be the result of applying the
// Strict Equality Comparison Algorithm to
// searchElement and elementK.
// iii. If same is true, return k.
if (k in o && o[k] === searchElement) {
return k;
}
k++;
}
return -1;
};
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment