├── 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 |
--------------------------------------------------------------------------------