From 83de923b55396e4dda14e9186883fbdbc8b39962 Mon Sep 17 00:00:00 2001 From: sashiwan Date: Fri, 29 May 2026 22:57:14 -0300 Subject: [PATCH 1/2] add typescript code blocks --- game/src/data/CodeBlocks.json | 361 +++++++++++++++++++++++++++++++++- 1 file changed, 360 insertions(+), 1 deletion(-) diff --git a/game/src/data/CodeBlocks.json b/game/src/data/CodeBlocks.json index 89c0251..206487a 100644 --- a/game/src/data/CodeBlocks.json +++ b/game/src/data/CodeBlocks.json @@ -1335,5 +1335,364 @@ " );\n", "}\n" ] + }, + { + "id": 69, + "title": "twoSum", + "language": "typescript", + "blocks": [ + "function twoSum(nums: number[], target: number): number[] {\n", + " const map = new Map();\n", + " \n", + " for (let i = 0; i < nums.length; i++) {\n", + " const other = target - nums[i];\n", + " \n", + " if (map.has(other)) {\n", + " return [map.get(other)!, i];\n", + " }\n", + " \n", + " map.set(nums[i], i);\n", + " }\n", + " \n", + " return [];\n", + "}\n" + ] + }, + { + "id": 70, + "title": "isPalindrome", + "language": "typescript", + "blocks": [ + "function isPalindrome(x: number): boolean {\n", + " if (x < 0) return false;\n", + " \n", + " let reverse = 0;\n", + " let xcopy = x;\n", + " \n", + " while (x > 0) {\n", + " reverse = reverse * 10 + (x % 10);\n", + " x = Math.floor(x / 10);\n", + " }\n", + " \n", + " return reverse === xcopy;\n", + "}\n" + ] + }, + { + "id": 71, + "title": "longestCommonPrefix", + "language": "typescript", + "blocks": [ + "function longestCommonPrefix(strs: string[]): string {\n", + " return strs.reduce((prev, next) => {\n", + " let i = 0;\n", + " while (prev[i] && next[i] && prev[i] === next[i]) i++;\n", + " return prev.slice(0, i);\n", + " });\n", + "}\n" + ] + }, + { + "id": 72, + "title": "mergeTwoLists", + "language": "typescript", + "blocks": [ + "class ListNode {\n", + " val: number;\n", + " next: ListNode | null;\n", + " constructor(val = 0, next: ListNode | null = null) {\n", + " this.val = val;\n", + " this.next = next;\n", + " }\n", + "}\n", + "\n", + "function mergeTwoLists(\n", + " list1: ListNode | null,\n", + " list2: ListNode | null\n", + "): ListNode | null {\n", + " const dummy = new ListNode();\n", + " let cur: ListNode = dummy;\n", + " \n", + " while (list1 && list2) {\n", + " if (list1.val > list2.val) {\n", + " cur.next = list2;\n", + " list2 = list2.next;\n", + " } else {\n", + " cur.next = list1;\n", + " list1 = list1.next;\n", + " }\n", + " cur = cur.next!;\n", + " }\n", + " \n", + " cur.next = list1 ?? list2;\n", + " return dummy.next;\n", + "}\n" + ] + }, + { + "id": 73, + "title": "removeDuplicates", + "language": "typescript", + "blocks": [ + "function removeDuplicates(nums: number[]): number {\n", + " let i = 1;\n", + " \n", + " for (let j = 1; j < nums.length; j++) {\n", + " if (nums[j] !== nums[i - 1]) {\n", + " nums[i] = nums[j];\n", + " i++;\n", + " }\n", + " }\n", + " \n", + " return i;\n", + "}\n" + ] + }, + { + "id": 74, + "title": "strStr", + "language": "typescript", + "blocks": [ + "function strStr(haystack: string, needle: string): number {\n", + " if (haystack.length < needle.length) return -1;\n", + " \n", + " for (let i = 0; i <= haystack.length - needle.length; i++) {\n", + " if (haystack.substring(i, i + needle.length) === needle) {\n", + " return i;\n", + " }\n", + " }\n", + " \n", + " return -1;\n", + "}\n" + ] + }, + { + "id": 75, + "title": "searchInsert", + "language": "typescript", + "blocks": [ + "function searchInsert(nums: number[], target: number): number {\n", + " let left = 0;\n", + " let right = nums.length - 1;\n", + " \n", + " while (left <= right) {\n", + " const mid = Math.floor((left + right) / 2);\n", + " \n", + " if (nums[mid] === target) return mid;\n", + " else if (nums[mid] > target) right = mid - 1;\n", + " else left = mid + 1;\n", + " }\n", + " \n", + " return left;\n", + "}\n" + ] + }, + { + "id": 76, + "title": "lengthOfLastWord", + "language": "typescript", + "blocks": [ + "function lengthOfLastWord(s: string): number {\n", + " let length = 0;\n", + " \n", + " for (let i = s.length - 1; i >= 0; i--) {\n", + " if (s[i] !== ' ') {\n", + " length++;\n", + " } else if (length > 0) {\n", + " break;\n", + " }\n", + " }\n", + " \n", + " return length;\n", + "}\n" + ] + }, + { + "id": 77, + "title": "plusOne", + "language": "typescript", + "blocks": [ + "function plusOne(digits: number[]): number[] {\n", + " for (let i = digits.length - 1; i >= 0; i--) {\n", + " if (digits[i] + 1 !== 10) {\n", + " digits[i] += 1;\n", + " return digits;\n", + " }\n", + " digits[i] = 0;\n", + " if (i === 0) {\n", + " digits.unshift(1);\n", + " return digits;\n", + " }\n", + " }\n", + " return digits;\n", + "}\n" + ] + }, + { + "id": 78, + "title": "addBinary", + "language": "typescript", + "blocks": [ + "function addBinary(a: string, b: string): string {\n", + " let carry = 0;\n", + " let res = '';\n", + " let i = a.length - 1;\n", + " let j = b.length - 1;\n", + " \n", + " while (i >= 0 || j >= 0 || carry) {\n", + " let sum = carry;\n", + " if (i >= 0) sum += parseInt(a[i--]);\n", + " if (j >= 0) sum += parseInt(b[j--]);\n", + " \n", + " res = (sum % 2) + res;\n", + " carry = Math.floor(sum / 2);\n", + " }\n", + " \n", + " return res;\n", + "}\n" + ] + }, + { + "id": 79, + "title": "mySqrt", + "language": "typescript", + "blocks": [ + "function mySqrt(x: number): number {\n", + " let left = 1;\n", + " let right = Math.floor(x / 2) + 1;\n", + " \n", + " while (left <= right) {\n", + " const mid = Math.floor((left + right) / 2);\n", + " \n", + " if (mid * mid > x) right = mid - 1;\n", + " else if (mid * mid < x) left = mid + 1;\n", + " else return mid;\n", + " }\n", + " \n", + " return right;\n", + "}\n" + ] + }, + { + "id": 80, + "title": "climbStairs", + "language": "typescript", + "blocks": [ + "function climbStairs(n: number): number {\n", + " if (n <= 3) return n;\n", + " \n", + " let prev1 = 3;\n", + " let prev2 = 2;\n", + " \n", + " for (let i = 3; i < n; i++) {\n", + " const cur = prev1 + prev2;\n", + " prev2 = prev1;\n", + " prev1 = cur;\n", + " }\n", + " \n", + " return prev1;\n", + "}\n" + ] + }, + { + "id": 81, + "title": "deleteDuplicates", + "language": "typescript", + "blocks": [ + "function deleteDuplicates(head: ListNode | null): ListNode | null {\n", + " let cur = head;\n", + " \n", + " while (cur && cur.next) {\n", + " if (cur.val === cur.next.val) {\n", + " cur.next = cur.next.next;\n", + " } else {\n", + " cur = cur.next;\n", + " }\n", + " }\n", + " \n", + " return head;\n", + "}\n" + ] + }, + { + "id": 82, + "title": "mergeSortedArray", + "language": "typescript", + "blocks": [ + "function merge(\n", + " nums1: number[], m: number,\n", + " nums2: number[], n: number\n", + "): void {\n", + " for (let i = m, j = 0; j < n; i++, j++) {\n", + " nums1[i] = nums2[j];\n", + " }\n", + " nums1.sort((a, b) => a - b);\n", + "}\n" + ] + }, + { + "id": 83, + "title": "inorderTraversal", + "language": "typescript", + "blocks": [ + "class TreeNode {\n", + " val: number;\n", + " left: TreeNode | null;\n", + " right: TreeNode | null;\n", + " constructor(val = 0, left: TreeNode | null = null, right: TreeNode | null = null) {\n", + " this.val = val;\n", + " this.left = left;\n", + " this.right = right;\n", + " }\n", + "}\n", + "\n", + "function inorderTraversal(root: TreeNode | null): number[] {\n", + " const res: number[] = [];\n", + " \n", + " function inorder(node: TreeNode | null): void {\n", + " if (!node) return;\n", + " inorder(node.left);\n", + " res.push(node.val);\n", + " inorder(node.right);\n", + " }\n", + " \n", + " inorder(root);\n", + " return res;\n", + "}\n" + ] + }, + { + "id": 84, + "title": "isSameTree", + "language": "typescript", + "blocks": [ + "function isSameTree(p: TreeNode | null, q: TreeNode | null): boolean {\n", + " if (!p && !q) return true;\n", + " if (p && q && p.val === q.val) {\n", + " return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);\n", + " }\n", + " return false;\n", + "}\n" + ] + }, + { + "id": 85, + "title": "isSymmetric", + "language": "typescript", + "blocks": [ + "function isSymmetric(root: TreeNode | null): boolean {\n", + " const isMirror = (n1: TreeNode | null, n2: TreeNode | null): boolean => {\n", + " if (!n1 && !n2) return true;\n", + " if (!n1 || !n2) return false;\n", + " return (\n", + " n1.val === n2.val &&\n", + " isMirror(n1.left, n2.right) &&\n", + " isMirror(n1.right, n2.left)\n", + " );\n", + " };\n", + " \n", + " return isMirror(root?.left ?? null, root?.right ?? null);\n", + "}\n" + ] } -] \ No newline at end of file +] From d0e17b94bf6b2135f08a4865f856da80b359a9a1 Mon Sep 17 00:00:00 2001 From: sashiwan Date: Fri, 29 May 2026 22:59:42 -0300 Subject: [PATCH 2/2] add typescript as new language option --- game/src/scenes/game.js | 121 ++++++++++++++--------- game/src/scenes/selectionScene.js | 159 ++++++++++++++++++++++-------- 2 files changed, 189 insertions(+), 91 deletions(-) diff --git a/game/src/scenes/game.js b/game/src/scenes/game.js index e359f1d..8f68316 100644 --- a/game/src/scenes/game.js +++ b/game/src/scenes/game.js @@ -13,17 +13,23 @@ import { MAX_BLOCKS, goalBlocks, maxMistakes, - ICON_START_Y + ICON_START_Y, } from "../constants.js"; import { toggleCapsLock, shouldUppercase, preventError, shuffle, - makeBlink + makeBlink, } from "../data/utilities.js"; -import { k, loadGtag, onBlockReached, onGameStart, MEASUREMENT_ID } from "../kaplay.js"; +import { + k, + loadGtag, + onBlockReached, + onGameStart, + MEASUREMENT_ID, +} from "../kaplay.js"; import { themes } from "../data/themes.js"; import { resizablePos } from "../components/resizablePos.js"; import { resizableRect } from "../components/resizableRect.js"; @@ -75,7 +81,6 @@ let cachedTokens = []; */ const gameScene = (params) => { - loadGtag(MEASUREMENT_ID, () => { onGameStart(); }); @@ -102,19 +107,20 @@ const gameScene = (params) => { let volumeIncrease; const filteredDialogs = dialogsData.filter( - item => (item.language || "default") === settings.language + (item) => (item.language || "default") === settings.language, ); const shuffledDialogs = shuffle([...filteredDialogs]); //save for analiytics - blockNamesString = shuffledDialogs.slice(0, MAX_BLOCKS).map(item => item.title); + blockNamesString = shuffledDialogs + .slice(0, MAX_BLOCKS) + .map((item) => item.title); // #region PLAYER & RIVAL VARIABLES /** * @type {PlayerState} */ const playerState = { - cursorPos: 0, line: "", curLineCount: 0, @@ -165,7 +171,10 @@ const gameScene = (params) => { const { cursorPos: rPos } = rivalState; if (errorCharsIndexes.includes(i)) return COLOR_TEXT_INCORRECT; - if (!settings.practiceMode && (i === rPos || (i > pPos - 1 && i < rPos + 1))) + if ( + !settings.practiceMode && + (i === rPos || (i > pPos - 1 && i < rPos + 1)) + ) return COLOR_TEXT_RIVAL; if (ch === " " || i > pPos - 1) return COLOR_TEXT_DEFAULT; @@ -188,13 +197,13 @@ const gameScene = (params) => { } const tokenRules = [ - { test: t => A.tags.test(t), color: T.tags }, - { test: t => A.numbers.test(t), color: T.numbers }, - { test: t => A.classes.test(t), color: T.classes }, - { test: t => A.functions.test(t), color: T.functions }, - { test: t => A.keywords.test(t), color: T.keywords }, - { test: t => A.strings.test(t), color: T.strings }, - { test: t => /^[A-Za-z_$][\w$]*$/.test(t), color: T.variables }, + { test: (t) => A.tags.test(t), color: T.tags }, + { test: (t) => A.numbers.test(t), color: T.numbers }, + { test: (t) => A.classes.test(t), color: T.classes }, + { test: (t) => A.functions.test(t), color: T.functions }, + { test: (t) => A.keywords.test(t), color: T.keywords }, + { test: (t) => A.strings.test(t), color: T.strings }, + { test: (t) => /^[A-Za-z_$][\w$]*$/.test(t), color: T.variables }, ]; for (const { test, color } of tokenRules) { if (test(token)) return k.Color.fromHex(color); @@ -250,12 +259,14 @@ const gameScene = (params) => { } } function updateAWPM() { - const totalEventsLast60 = eventBuffer.reduce((sum, count) => sum + count, 0); + const totalEventsLast60 = eventBuffer.reduce( + (sum, count) => sum + count, + 0, + ); actual_awpm = totalEventsLast60 / 5; } function escapeForRender(str) { - return str .replace(/\\/g, "\\\\") .replace(/\[/g, "\\[") @@ -307,8 +318,10 @@ const gameScene = (params) => { const textObj = titleTexts[i]; if (shuffledDialogs[currentBlockIndex + i]) { textObj.text = shuffledDialogs[currentBlockIndex + i].title; - textObj.color = (currentBlockIndex + i) <= currentBlockIndex ? k.rgb(127, 134, 131) : k.WHITE; - + textObj.color = + currentBlockIndex + i <= currentBlockIndex + ? k.rgb(127, 134, 131) + : k.WHITE; } else { textObj.text = ""; } @@ -415,8 +428,8 @@ const gameScene = (params) => { }; const texts = dialogsData - .filter(item => (item.language || "default") === settings.language) - .map(item => ({ + .filter((item) => (item.language || "default") === settings.language) + .map((item) => ({ title: item.title, language: item.language || "default", })); @@ -431,8 +444,8 @@ const gameScene = (params) => { resizablePos(() => k.vec2( k.width() * 0.02, - k.height() * (ICON_START_Y + SPACING * index) - ) + k.height() * (ICON_START_Y + SPACING * index), + ), ), k.opacity(1), k.z(55), @@ -444,8 +457,8 @@ const gameScene = (params) => { resizablePos(() => k.vec2( k.width() * 0.05, - k.height() * (TEXT_START_Y + SPACING * index) - ) + k.height() * (TEXT_START_Y + SPACING * index), + ), ), k.color(k.WHITE), k.opacity(1), @@ -458,8 +471,7 @@ const gameScene = (params) => { button_muteON.opacity = 0; button_muteOFF.opacity = 1; updateMusicVolume(); - } - else { + } else { button_muteON.opacity = 1; button_muteOFF.opacity = 0; updateMusicVolume(); @@ -530,10 +542,11 @@ const gameScene = (params) => { k.opacity(0), ]); - const textboxTextPos = () => { - return k.vec2(textPadding).sub(0, lineHeight * (JUMP_AFTER * jumpCount)); - } + return k + .vec2(textPadding) + .sub(0, lineHeight * (JUMP_AFTER * jumpCount)); + }; const textboxText = textboxBackParent.add([ k.text("", { @@ -557,12 +570,11 @@ const gameScene = (params) => { const player = rival ? rivalState : playerState; const displayLine = player.curLineCount - jumpCount * JUMP_AFTER; const x = player.curCharInLine * fontWidth; - const y = displayLine * actualLineHeight - + cursorVerticalOffset - + CURSOR_EXTRA_OFFSET; - return textboxBackParent.pos - .add(textboxText.pos) - .add(x, y); + const y = + displayLine * actualLineHeight + + cursorVerticalOffset + + CURSOR_EXTRA_OFFSET; + return textboxBackParent.pos.add(textboxText.pos).add(x, y); }; const cursorPointer = k.add([ @@ -637,7 +649,6 @@ const gameScene = (params) => { const steps = 4; if (completedBlocks > 0) { - const t = Math.min(completedBlocks, steps) / steps; rivalSpeed = startSpeed * Math.pow(endSpeed / startSpeed, t); } else { @@ -658,7 +669,7 @@ const gameScene = (params) => { const currentDialog = getCurrentDialog(); const lang = currentDialog.language ?? "default"; - theme = themes.find(t => t.name === lang) || themes[0]; + theme = themes.find((t) => t.name === lang) || themes[0]; const currentBlocks = currentDialog.blocks; curBlockData.lineCount = currentBlocks.length; originalText = currentBlocks.join(""); @@ -692,7 +703,7 @@ const gameScene = (params) => { return char; } }) - .join("") + .join(""); renderedText = escapeForRender(renderedText); textboxText.text = renderedText; } @@ -723,8 +734,7 @@ const gameScene = (params) => { player.curLineCount++; if (JUMP_AFTER == 1) { jumpCount++; - } - else if (playerState.curLineCount >= JUMP_AFTER * (jumpCount + 1)) { + } else if (playerState.curLineCount >= JUMP_AFTER * (jumpCount + 1)) { jumpCount++; } if (!isRival) { @@ -759,9 +769,18 @@ const gameScene = (params) => { function analitycs_calculate() { time_text.text = startTime.toFixed(1); if (startTime > 0 && totalCorrectChars > 5) { - actual_wpm = (totalCorrectChars && startTime > 1) ? (totalCorrectChars / 5) / (startTime / 60) : 0; - actual_lpm = (totalCorrectlines && startTime > 1) ? (totalCorrectlines) / (startTime / 60) : 0; - actual_acc = totalTypedCharacters > 0 ? (totalCorrectChars / totalTypedCharacters) * 100 : 100; + actual_wpm = + totalCorrectChars && startTime > 1 + ? totalCorrectChars / 5 / (startTime / 60) + : 0; + actual_lpm = + totalCorrectlines && startTime > 1 + ? totalCorrectlines / (startTime / 60) + : 0; + actual_acc = + totalTypedCharacters > 0 + ? (totalCorrectChars / totalTypedCharacters) * 100 + : 100; if (isNaN(actual_acc)) { actual_acc = 100; @@ -769,7 +788,6 @@ const gameScene = (params) => { wmp_text.text = Math.round(actual_wpm || 0).toString(); } - } const BUFFER_SIZE = 60; @@ -795,7 +813,8 @@ const gameScene = (params) => { }); k.onKeyPress((keyPressed) => { - const prevChar = playerState.cursorPos > 0 ? fixedText[playerState.cursorPos] : ''; + const prevChar = + playerState.cursorPos > 0 ? fixedText[playerState.cursorPos] : ""; if (prevChar === "\n") return; const correctChar = fixedText[playerState.cursorPos]; @@ -826,7 +845,8 @@ const gameScene = (params) => { addCorrectEvent(); nextChar(); } else { - if (errorCharsIndexes.length > maxMistakes) return preventError(k, settings); + if (errorCharsIndexes.length > maxMistakes) + return preventError(k, settings); errorCharsIndexes.push(playerState.cursorPos); errorCharsReplaces[playerState.cursorPos] = errorKey; @@ -836,7 +856,10 @@ const gameScene = (params) => { if (!settings.mute) k.play("wrong_typing"); totalIcorrectCorrectChars++; } - if (!playerStartedTyping && (totalCorrectChars > 0 || totalIcorrectCorrectChars > 0)) { + if ( + !playerStartedTyping && + (totalCorrectChars > 0 || totalIcorrectCorrectChars > 0) + ) { playerStartedTyping = true; } }); @@ -891,4 +914,4 @@ const gameScene = (params) => { moveArrow(); }; -k.scene("game", gameScene); \ No newline at end of file +k.scene("game", gameScene); diff --git a/game/src/scenes/selectionScene.js b/game/src/scenes/selectionScene.js index b51f6c8..db634fc 100644 --- a/game/src/scenes/selectionScene.js +++ b/game/src/scenes/selectionScene.js @@ -1,7 +1,7 @@ import { escapeBackslashes, preventError, - setCapsLockActive + setCapsLockActive, } from "../data/utilities.js"; import { getMute, saveMute } from "../systems/preferences.js"; import { resizablePos } from "../components/resizablePos.js"; @@ -11,11 +11,12 @@ export const settings = { practiceMode: false, isCapsOn: false, rivalSpeed: 0, - language: "js" + language: "js", }; function isMobile() { - return /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i - .test(navigator.userAgent); + return /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( + navigator.userAgent, + ); } k.scene("selection", () => { k.loadSprite("icon_05", "/sprites/icon_05.png"); @@ -33,7 +34,7 @@ k.scene("selection", () => { const textY = k.height() * 0.85; const underscoreY = k.height() * 0.86; const boxY = k.height() * 0.66; - const boxX = k.width() * 0.30 - 10; + const boxX = k.width() * 0.3 - 10; const boxCenterX = boxX + boxWidth / 2; const arrowYOffset = 0; const buttonLeftX = k.width() * 0.35 - 40; @@ -68,7 +69,7 @@ k.scene("selection", () => { } isMobile(); k.add([ - k.pos(boxCenterX, k.height() * 0.50), + k.pos(boxCenterX, k.height() * 0.5), k.anchor("center"), k.text("Get faster and better at technical interviewing", { size: 22 }), k.color(k.WHITE), @@ -88,12 +89,57 @@ k.scene("selection", () => { k.z(18), ]); - const outsideBox = k.add([k.rect(810, 260, { radius: 0 }), k.pos(k.width() * 0.30 - 15, boxY), k.color(k.rgb(52, 53, 54)), k.z(20), k.opacity(0.3)]); - const outerBox = k.add([k.rect(790, 230, { radius: 1 }), k.pos(k.width() * 0.30 - 5, boxY + 20), k.color(0, 0, 0), k.z(20), k.opacity(1)]); - const selecttext = k.add([k.anchor("left"), k.text("", { size: fontsize }), resizablePos(() => k.vec2(buttonLeftX, buttonTopY)), k.opacity(1), k.z(21)]); - const selecttext2 = k.add([k.anchor("left"), k.text("", { size: fontsize }), resizablePos(() => k.vec2(buttonLeftX, buttonTopY + buttonGap)), k.opacity(1), k.z(21)]); - const selecttext3 = k.add([k.anchor("left"), k.text("", { size: fontsize }), resizablePos(() => k.vec2(buttonLeftX, buttonTopY + buttonGap * 2)), k.opacity(1), k.z(21)]); - const selecttext4 = k.add([k.anchor("left"), k.text("", { size: fontsize }), resizablePos(() => k.vec2(buttonLeftX + 250, buttonTopY + buttonGap)), k.opacity(1), k.z(21)]); + const outsideBox = k.add([ + k.rect(810, 260, { radius: 0 }), + k.pos(k.width() * 0.3 - 15, boxY), + k.color(k.rgb(52, 53, 54)), + k.z(20), + k.opacity(0.3), + ]); + const outerBox = k.add([ + k.rect(790, 230, { radius: 1 }), + k.pos(k.width() * 0.3 - 5, boxY + 20), + k.color(0, 0, 0), + k.z(20), + k.opacity(1), + ]); + const selecttext = k.add([ + k.anchor("left"), + k.text("", { size: fontsize }), + resizablePos(() => k.vec2(buttonLeftX, buttonTopY)), + k.opacity(1), + k.z(21), + ]); + const selecttext2 = k.add([ + k.anchor("left"), + k.text("", { size: fontsize }), + resizablePos(() => k.vec2(buttonLeftX, buttonTopY + buttonGap)), + k.opacity(1), + k.z(21), + ]); + const selecttext3 = k.add([ + k.anchor("left"), + k.text("", { size: fontsize }), + resizablePos(() => k.vec2(buttonLeftX, buttonTopY + buttonGap * 2)), + k.opacity(1), + k.z(21), + ]); + const selecttext4 = k.add([ + k.anchor("left"), + k.text("", { size: fontsize }), + resizablePos(() => k.vec2(buttonLeftX + 250, buttonTopY + buttonGap)), + k.opacity(1), + k.z(21), + ]); + const selecttext5 = k.add([ + k.anchor("left"), + k.text("", { size: fontsize }), + resizablePos(() => + k.vec2(buttonLeftX + 250, buttonTopY + buttonGap * 2), + ), + k.opacity(1), + k.z(21), + ]); const button_muteON = k.add([ k.sprite("muteON"), @@ -129,27 +175,36 @@ k.scene("selection", () => { commands = ["yes", "no"]; break; case 2: - commands = ["javascript", "python", "golang"]; + commands = ["javascript", "typescript", "python", "golang"]; break; case 3: commands = ["interview", "practice"]; break; } - selecttext.text = stage === 0 ? "start" - : stage === 1 ? "Play with Audio?" - : stage === 2 ? "Language" + selecttext.text = + stage === 0 + ? "start" + : stage === 1 + ? "Play with Audio?" + : stage === 2 + ? "Language" : "Game Mode"; selecttext2.text = commands[0]; selecttext3.text = commands[1]; - if (stage === 2) selecttext4.text = commands[2]; - else selecttext4.text = ""; + if (stage === 2) { + selecttext4.text = commands[2]; + selecttext5.text = commands[3]; + } else { + selecttext4.text = ""; + selecttext5.text = ""; + } updateTextColors(); } function calcNewTarget(input) { if (input !== "") { - const match = commands.find(cmd => - cmd.startsWith(input.toLowerCase()) + const match = commands.find((cmd) => + cmd.startsWith(input.toLowerCase()), ); if (match) return match; } @@ -157,7 +212,7 @@ k.scene("selection", () => { const defaults = { 1: "yes", 2: "javascript", - 3: "interview" + 3: "interview", }; return defaults[stage] || "start"; @@ -167,24 +222,29 @@ k.scene("selection", () => { const newY = targetObj.pos.y + arrowYOffset; const newX = targetObj.pos.x + targetObj.text.length * 16; commandArrow.pos = k.vec2(newX, newY); - commandArrow.animate("pos", [k.vec2(newX, newY), k.vec2(newX + 10, newY)], { - duration: 0.5, - loop: true, - direction: "ping-pong", - }); + commandArrow.animate( + "pos", + [k.vec2(newX, newY), k.vec2(newX + 10, newY)], + { + duration: 0.5, + loop: true, + direction: "ping-pong", + }, + ); } let targetText = "start"; let maxLength = targetText.length; const letterSpacing = 14; - const fixedStartX = k.width() / 2.88 - ((maxLength - 1) * letterSpacing) / 2; + const fixedStartX = + k.width() / 2.88 - ((maxLength - 1) * letterSpacing) / 2; let letterObjects = []; let underscoreObjects = []; updateTextColors(); function createLetterObjects() { - letterObjects.forEach(obj => k.destroy(obj)); - underscoreObjects.forEach(obj => k.destroy(obj)); + letterObjects.forEach((obj) => k.destroy(obj)); + underscoreObjects.forEach((obj) => k.destroy(obj)); letterObjects = []; underscoreObjects = []; @@ -217,6 +277,8 @@ k.scene("selection", () => { selecttext.color = k.rgb(255, 255, 255); selecttext2.color = k.rgb(255, 255, 255); selecttext3.color = k.rgb(255, 255, 255); + selecttext4.color = k.rgb(255, 255, 255); + selecttext5.color = k.rgb(255, 255, 255); const cmdLower = targetText.toLowerCase(); let commandList; @@ -226,26 +288,27 @@ k.scene("selection", () => { commandList = [ { obj: selecttext, label: "start" }, { obj: selecttext2, label: "about" }, - { obj: selecttext3, label: "github" } + { obj: selecttext3, label: "github" }, ]; break; case 1: commandList = [ { obj: selecttext2, label: "yes" }, - { obj: selecttext3, label: "no" } + { obj: selecttext3, label: "no" }, ]; break; case 2: commandList = [ { obj: selecttext2, label: "javascript" }, - { obj: selecttext3, label: "python" }, - { obj: selecttext4, label: "golang" } + { obj: selecttext3, label: "typescript" }, + { obj: selecttext4, label: "python" }, + { obj: selecttext5, label: "golang" }, ]; break; case 3: commandList = [ { obj: selecttext2, label: "interview" }, - { obj: selecttext3, label: "practice" } + { obj: selecttext3, label: "practice" }, ]; break; default: @@ -253,10 +316,12 @@ k.scene("selection", () => { } let selected = null; + let selectedLabel = null; commandList.forEach(({ obj, label }) => { if (cmdLower === label) { obj.color = k.rgb(3, 255, 87); selected = obj; + selectedLabel = label; } }); @@ -322,8 +387,10 @@ k.scene("selection", () => { const maxError = 3; const isTooLongTotal = input.length > targetText.length; - const isGrowingWithErrors = input.length > previousInput.length && localErrorCount >= maxError; - const hasAdvancedPastError = localErrorCount >= 2 && input.length > lastErrorIndex + 1; + const isGrowingWithErrors = + input.length > previousInput.length && localErrorCount >= maxError; + const hasAdvancedPastError = + localErrorCount >= 2 && input.length > lastErrorIndex + 1; if (isTooLongTotal || isGrowingWithErrors) { preventError(k, settings); @@ -351,14 +418,17 @@ k.scene("selection", () => { const char = input[i]; const displayChar = !char ? correct - : (char === " " && correct !== " " ? "_" : char); + : char === " " && correct !== " " + ? "_" + : char; letterObj.text = escapeBackslashes(displayChar); letterObj.color = !char ? k.rgb(128, 128, 128) - : (char.toLowerCase() !== correct.toLowerCase() || (char === " " && correct !== " ")) - ? k.rgb(255, 0, 0) - : k.rgb(3, 255, 87); + : char.toLowerCase() !== correct.toLowerCase() || + (char === " " && correct !== " ") + ? k.rgb(255, 0, 0) + : k.rgb(3, 255, 87); }); underscoreObjects.forEach((uObj, i) => { @@ -372,10 +442,12 @@ k.scene("selection", () => { switch (input.toLowerCase()) { case "javascript": + case "typescript": case "python": case "golang": if (stage === 2) { settings.language = input.toLowerCase(); + advanceStage(); updateStageCommands(); const candidate = calcNewTarget(""); @@ -489,7 +561,10 @@ k.scene("selection", () => { function setupKeyboardInput() { handleKeydown = (e) => { - if (e.getModifierState && typeof e.getModifierState === "function") { + if ( + e.getModifierState && + typeof e.getModifierState === "function" + ) { settings.isCapsOn = e.getModifierState("CapsLock"); } if (e.key.length === 1) { @@ -515,7 +590,7 @@ k.scene("selection", () => { window.addEventListener("keydown", handleKeydown); } - k.onKeyPress('backspace', () => { + k.onKeyPress("backspace", () => { if (!rawInput) return; rawInput = rawInput.slice(0, -1); name.text = escapeBackslashes(rawInput);