diff --git a/main.go b/main.go index a5324f8..8397356 100644 --- a/main.go +++ b/main.go @@ -52,8 +52,8 @@ func main() { app.Get("/", func(c *fiber.Ctx) error { return c.Render("index", fiber.Map{}) }) - app.Get("/end", func(c *fiber.Ctx) error { - return c.Render("end", fiber.Map{}) + app.Get("/results", func(c *fiber.Ctx) error { + return c.Render("results", fiber.Map{}) }) app.Get("/:qid", func(c *fiber.Ctx) error { qID, err := c.ParamsInt("qid") @@ -71,7 +71,7 @@ func main() { return err } - nextURL := prefix + "/end" + nextURL := prefix + "/results" reachedEnd := true if idx+1 < len(questions.Questions) { nextURL = fmt.Sprintf("%v/%d", prefix, idx+2) diff --git a/public/scripts/controllers/yourscore.js b/public/scripts/controllers/yourscore.js new file mode 100644 index 0000000..dba36be --- /dev/null +++ b/public/scripts/controllers/yourscore.js @@ -0,0 +1,97 @@ +import { Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js"; +import { gameState } from "./gamestate.js"; + +const ANIMATION_DURATION = 1500.0; +const MAX_QUESTIONS = 10; +const CIRCLE_RADIUS = 50; + +export default class extends Controller { + static targets = ['path', 'pathBack', 'count', 'postScore', 'rank']; + + connect() { + this._startT = 0; + + requestAnimationFrame(this._startAnimating.bind(this)); + } + + _startAnimating(t) { + this._finalScore = 10; + + this._startT = t; + this._finalArcPostition = 360 * this._finalScore / MAX_QUESTIONS; + + this._currentlyDisplayedScore = 0; + this.countTarget.innerText = "0%"; + requestAnimationFrame(this._animateFrame.bind(this)); + } + + _animateFrame(t) { + let tt = t - this._startT; + let tScaled = tt / ANIMATION_DURATION; + + if (tt >= ANIMATION_DURATION) { + if (this._finalArcPostition == 360) { + this.pathTarget.setAttribute("d", this._describeCircle(CIRCLE_RADIUS, CIRCLE_RADIUS, CIRCLE_RADIUS - 5)); + } else { + this.pathTarget.setAttribute("d", this._describeArc(CIRCLE_RADIUS, CIRCLE_RADIUS, CIRCLE_RADIUS - 5, 0, this._finalArcPostition)); + } + + let scoreToDisplay = (this._finalScore * 100 / MAX_QUESTIONS) | 0; + this.countTarget.innerText = `${scoreToDisplay}%`; + window.setTimeout(() => this._showPostStore(), 1); + return; + } + + let arcT = Math.sin(tt / ANIMATION_DURATION * Math.PI / 2.0); + + this.pathTarget.setAttribute("d", this._describeArc(CIRCLE_RADIUS, CIRCLE_RADIUS, CIRCLE_RADIUS - 5, 0, this._finalArcPostition * arcT)); + + let scoreToDisplay = (arcT * this._finalScore * 100 / MAX_QUESTIONS) | 0; + if (scoreToDisplay != this._currentlyDisplayedScore) { + this._currentlyDisplayedScore = scoreToDisplay; + this.countTarget.innerText = `${scoreToDisplay}%`; + } + + requestAnimationFrame(this._animateFrame.bind(this)); + } + + _polarToCartesian(centerX, centerY, radius, angleInDegrees) { + var angleInRadians = (angleInDegrees - 180) * Math.PI / 180; + + return { + x: centerX + (radius * Math.cos(angleInRadians)), + y: centerY + (radius * Math.sin(angleInRadians)) + }; + } + + _describeCircle(x, y, radius) { + var start = this._polarToCartesian(x, y, radius, 0); + var mid = this._polarToCartesian(x, y, radius, 180); + var end = this._polarToCartesian(x, y, radius, 360); + + var d = [ + "M", start.x, start.y, + "A", radius, radius, 0, 0, 0, mid.x, mid.y, + "A", radius, radius, 0, 1, 0, end.x, end.y, + ].join(" "); + return d; + } + + _describeArc(x, y, radius, startAngle, endAngle){ + var start = this._polarToCartesian(x, y, radius, endAngle); + var end = this._polarToCartesian(x, y, radius, startAngle); + var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1"; + + var d = [ + "M", start.x, start.y, + "A", radius, radius, 0, largeArcFlag, 0, end.x, end.y + ].join(" "); + return d; + } + + _showPostStore() { + this.rankTarget.innerText = "Developer"; + + this.postScoreTarget.classList.remove("hidden"); + } +} \ No newline at end of file diff --git a/public/scripts/main.js b/public/scripts/main.js index 56298f7..f797a9d 100644 --- a/public/scripts/main.js +++ b/public/scripts/main.js @@ -2,8 +2,10 @@ import { Application } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus. import ClearStateController from "./controllers/clearstate.js"; import PickerController from "./controllers/picker.js"; +import YourScoreController from "./controllers/yourscore.js"; window.Stimulus = Application.start(); Stimulus.register("picker", PickerController); -Stimulus.register("clearstate", ClearStateController); \ No newline at end of file +Stimulus.register("clearstate", ClearStateController); +Stimulus.register("yourscore", YourScoreController); \ No newline at end of file diff --git a/public/style.css b/public/style.css index a31d0af..ff9a432 100644 --- a/public/style.css +++ b/public/style.css @@ -1,3 +1,7 @@ +:root { + --score-color: green; +} + div.offscreen { position: fixed; left: -100px; @@ -85,6 +89,65 @@ input[type=radio] { display: none !important; } +.vspacer { + height: 2em; +} + +/** + * Your score + */ +div.score-wrapper { + text-align: center; +} + +div.yourscore { + margin-inline: auto; + position: relative; + text-align: left; + + width: 250px; + height: 250px; +} + +div.yourscore svg { + position: absolute; + + width: 100%; + height: 100%; + z-index: -10; +} + +div.yourscore svg path { + stroke: #ccc; +} + +div.yourscore svg path.score { + stroke: var(--score-color); +} + + +div.yourscore .counter { + position: absolute; + font-size: 4em; + + top: calc(50% - 0.75em); + left: 0; + width: 100%; + text-align: center; + color: var(--score-color); + + z-index: 10; +} + +div.post-score { + margin-block-start: 2em; +} + +div.rank { + font-size: 1.5em; + text-align: center; +} + /** * Page transition */ diff --git a/views/end.html b/views/end.html deleted file mode 100644 index ea4de5e..0000000 --- a/views/end.html +++ /dev/null @@ -1,3 +0,0 @@ -
You reached the end
diff --git a/views/results.html b/views/results.html new file mode 100644 index 0000000..9fe08d9 --- /dev/null +++ b/views/results.html @@ -0,0 +1,22 @@ +Your final score is
+