├── david_and_anton.css ├── index.html └── david_and_anton.js /david_and_anton.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | overflow: hidden; 5 | } 6 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | The David and Anton Puzzle 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /david_and_anton.js: -------------------------------------------------------------------------------- 1 | const ca = document.getElementById("canvas"); 2 | const ct = ca.getContext("2d"); 3 | let w, h; 4 | 5 | let diff = -10; 6 | 7 | function resize() { 8 | w = ca.width = window.innerWidth; 9 | h = ca.height = window.innerHeight; 10 | window.requestAnimationFrame(draw); 11 | } 12 | 13 | function text(ct, text, x, y) { 14 | ct.save(); 15 | let scale = 0.25; 16 | ct.scale(scale, -scale); 17 | ct.fillText(text, (x + 3) / scale, -y / scale); 18 | ct.restore(); 19 | } 20 | 21 | function divisions(ct, x, y1, y2, divs) { 22 | let step = (y2 - y1) / divs; 23 | ct.save(); 24 | ct.setLineDash([]); 25 | 26 | ct.beginPath(); 27 | ct.moveTo(x, y2); 28 | ct.lineTo(x, y1); 29 | ct.strokeStyle = "green"; 30 | ct.stroke(); 31 | 32 | ct.strokeStyle = "black"; 33 | ct.beginPath(); 34 | for (let i = 0; i < divs + 1; i++) { 35 | ct.moveTo(x - 1, y1 + i * step); 36 | ct.lineTo(x + 1, y1 + i * step); 37 | } 38 | ct.stroke(); 39 | ct.restore(); 40 | } 41 | 42 | function draw() { 43 | ct.clearRect(0, 0, ca.width, ca.height); 44 | let minX = -65 / 2 - 20; 45 | let maxX = 40; 46 | let minY = -15.0; 47 | let maxY = 65 + 15.0; 48 | 49 | ct.save(); 50 | ct.setLineDash([]); 51 | ct.font = "12pt sans-serif"; 52 | ct.textBaseline = "middle"; 53 | 54 | ct.translate(ca.width / 2, ca.height / 2); 55 | let hscale = ca.height / 120; 56 | let wscale = ca.width / 100; 57 | let scale = Math.min(hscale, wscale); 58 | ct.scale(scale, -scale); 59 | ct.translate(0, -30); 60 | 61 | // Coordinate system 62 | ct.beginPath(); 63 | ct.moveTo(minX, 0); 64 | ct.lineTo(maxX, 0); 65 | ct.moveTo(0, minY); 66 | ct.lineTo(0, maxY); 67 | ct.lineWidth = 0.5; 68 | ct.strokeStyle = "gray"; 69 | ct.stroke(); 70 | 71 | // 65 years 72 | ct.beginPath(); 73 | ct.lineWidth = 0.25; 74 | ct.strokeStyle = "brown"; 75 | ct.moveTo(-65.0 / 2 + minY / 2, minY); 76 | ct.lineTo((maxY - 65) / 2, maxY); 77 | ct.stroke(); 78 | text(ct, "Combined age", (maxY - 65) / 2, maxY); 79 | 80 | ct.beginPath(); 81 | ct.stroke(); 82 | 83 | let antonToday = diff / 2 + 65.0 / 2; 84 | let antonBorn = -antonToday; 85 | let davidToday = -diff / 2 + 65.0 / 2; 86 | let davidBorn = -davidToday; 87 | let anton2 = davidToday / 3; 88 | let anton2Day = antonBorn + anton2; 89 | let david2 = anton2 - diff; 90 | let anton4 = david2 * 2; 91 | let time4 = antonBorn + anton4; 92 | let david1 = anton4 / 3; 93 | let time1 = davidBorn + david1; 94 | let anton1 = time1 - antonBorn; 95 | 96 | // Correspondence lines 97 | ct.beginPath(); 98 | ct.moveTo(0, anton2); 99 | ct.lineTo(anton2Day, anton2); 100 | ct.moveTo(anton2Day, david2); 101 | ct.lineTo(time4, david2); 102 | ct.moveTo(time4, anton4); 103 | ct.lineTo(time1, anton4); 104 | // ct.moveTo(anton2Day, minY); 105 | // ct.lineTo(anton2Day, maxY); 106 | ct.strokeStyle = "orange"; 107 | ct.lineWidth = 0.25; 108 | ct.setLineDash([2, 1]); 109 | ct.stroke(); 110 | 111 | 112 | 113 | // Anton's life so far 114 | ct.setLineDash([]); 115 | ct.beginPath(); 116 | ct.moveTo(antonBorn, 0); 117 | ct.lineTo(0, antonToday); 118 | ct.strokeStyle = "red"; 119 | ct.stroke(); 120 | 121 | // David's life so far 122 | ct.beginPath(); 123 | ct.moveTo(davidBorn, 0); 124 | ct.lineTo(0, davidToday); 125 | ct.strokeStyle = "blue"; 126 | ct.stroke(); 127 | 128 | // Extension of Anton's life 129 | ct.beginPath(); 130 | ct.moveTo(-65.0 / 2, -65.0 / 2 - antonBorn); 131 | ct.lineTo(antonBorn, 0); 132 | ct.moveTo(0, antonToday); 133 | ct.lineTo(time4, anton4); 134 | ct.strokeStyle = "red"; 135 | ct.setLineDash([1, 1]); 136 | ct.stroke(); 137 | 138 | // Time 2 139 | ct.save(); 140 | ct.beginPath(); 141 | ct.lineWidth = 0.2; 142 | ct.setLineDash([]); 143 | ct.strokeStyle = 'gray'; 144 | ct.moveTo(minX, davidToday); 145 | ct.lineTo(maxX, davidToday); 146 | ct.moveTo(minX, antonToday); 147 | ct.lineTo(maxX, antonToday); 148 | ct.moveTo(minX, 65); 149 | ct.lineTo(maxX, 65); 150 | ct.stroke(); 151 | ct.restore(); 152 | 153 | divisions(ct, -65.0 / 2, -diff / 2, diff / 2, 2); 154 | divisions(ct, time4, 0, anton4, 2); 155 | divisions(ct, time1, 0, anton4, 3); 156 | divisions(ct, time1, 0, david1, 3); 157 | divisions(ct, 0, 0, davidToday, 3); 158 | divisions(ct, anton2Day, anton2, david2, 1); 159 | 160 | text(ct, "65 years", maxX, 65); 161 | text(ct, "Anton: " + antonToday, maxX, antonToday); 162 | text(ct, "David: " + davidToday, maxX, davidToday); 163 | text(ct, Math.round(david1 / anton1 * 1e3) / 1e3, time1, anton1); 164 | 165 | ct.restore(); 166 | } 167 | 168 | function change(delta) { 169 | diff += delta; 170 | diff = Math.round(diff * 10.0) / 10.0; 171 | window.requestAnimationFrame(draw); 172 | } 173 | 174 | function wheel(e) { 175 | change(0.2 * Math.sign(e.deltaY)); 176 | } 177 | 178 | function keydown(e) { 179 | if (e.key == "+" || e.key == "ArrowUp" || e.key == "ArrowRight") 180 | change(-0.2); 181 | if (e.key == "-" || e.key == "ArrowDown" || e.key == "ArrowLeft") 182 | change(0.2); 183 | } 184 | 185 | window.addEventListener("load", resize); 186 | window.addEventListener("resize", resize) 187 | window.addEventListener("wheel", wheel); 188 | window.addEventListener("keydown", keydown); 189 | --------------------------------------------------------------------------------