From c88ce51e55a6eb8fe1837dd661b7e35c58f2f306 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Nov 2025 12:21:31 +0000 Subject: [PATCH] feat: Implement JavaScript problem data for Bug Sniper game Add problem data structure with 8 problems across 3 difficulty levels: Level 1 (3 problems): - Variable redeclaration error - Infinite loop detection - Date.getMonth() misuse Level 2 (3 problems): - Missing class method - Set/Map incorrect usage - String vs number type confusion Level 3 (2 problems): - Object.entries with dot notation issue - Static vs instance method confusion with Array.find Also implemented getProblems() function to dynamically load problems using Vite's import.meta.glob for efficient problem management. --- app/problems/index.ts | 45 +++++++++++-- app/problems/javascript/level1/js-l1-001.json | 25 ++++++++ app/problems/javascript/level1/js-l1-002.json | 27 ++++++++ app/problems/javascript/level1/js-l1-003.json | 26 ++++++++ app/problems/javascript/level2/js-l2-001.json | 37 +++++++++++ app/problems/javascript/level2/js-l2-002.json | 38 +++++++++++ app/problems/javascript/level2/js-l2-003.json | 41 ++++++++++++ app/problems/javascript/level3/js-l3-001.json | 45 +++++++++++++ app/problems/javascript/level3/js-l3-002.json | 64 +++++++++++++++++++ 9 files changed, 344 insertions(+), 4 deletions(-) create mode 100644 app/problems/javascript/level1/js-l1-001.json create mode 100644 app/problems/javascript/level1/js-l1-002.json create mode 100644 app/problems/javascript/level1/js-l1-003.json create mode 100644 app/problems/javascript/level2/js-l2-001.json create mode 100644 app/problems/javascript/level2/js-l2-002.json create mode 100644 app/problems/javascript/level2/js-l2-003.json create mode 100644 app/problems/javascript/level3/js-l3-001.json create mode 100644 app/problems/javascript/level3/js-l3-002.json diff --git a/app/problems/index.ts b/app/problems/index.ts index 3c493c4..8c9b950 100644 --- a/app/problems/index.ts +++ b/app/problems/index.ts @@ -25,16 +25,53 @@ export type Problem = { issues: Issue[]; }; +// Import all problem JSON files using Vite's glob import +const problemModules = import.meta.glob<{ default: Problem }>( + './javascript/level*/*.json', + { eager: true } +); + +// Cache for loaded problems +const problemsCache: Problem[] = []; +let cacheInitialized = false; + +/** + * Initialize problems cache + */ +function initializeProblemsCache() { + if (cacheInitialized) return; + + for (const path in problemModules) { + const problem = problemModules[path].default; + problemsCache.push(problem); + } + + cacheInitialized = true; +} + /** * Get problems by language and level * @param lang - Code language or 'all' * @param level - Problem difficulty level (1, 2, or 3) * @returns Array of problems matching the criteria */ -export function getProblems(_lang: CodeLanguageOrAll, _level: number): Problem[] { - // TODO: Implement problem loading logic - // This will be implemented in feature/problem-loader - return []; +export function getProblems(lang: CodeLanguageOrAll, level: number): Problem[] { + initializeProblemsCache(); + + return problemsCache.filter((problem) => { + const langMatch = lang === 'all' || problem.codeLanguage === lang; + const levelMatch = problem.level === level; + return langMatch && levelMatch; + }); +} + +/** + * Get all problems + * @returns Array of all problems + */ +export function getAllProblems(): Problem[] { + initializeProblemsCache(); + return [...problemsCache]; } /** diff --git a/app/problems/javascript/level1/js-l1-001.json b/app/problems/javascript/level1/js-l1-001.json new file mode 100644 index 0000000..f2dc07d --- /dev/null +++ b/app/problems/javascript/level1/js-l1-001.json @@ -0,0 +1,25 @@ +{ + "id": "js-l1-001", + "codeLanguage": "javascript", + "level": 1, + "code": [ + "function processUserData(userId) {", + " const userId = getUserId();", + " const userData = fetchData(userId);", + " return userData;", + "}" + ], + "issues": [ + { + "id": "js-l1-001-1", + "lines": [2], + "type": "bug", + "severity": "critical", + "score": 4, + "description": { + "ja": "引数userIdと同名の変数を再定義しているため、SyntaxErrorが発生します", + "en": "Redeclaring variable 'userId' with the same name as the parameter causes a SyntaxError" + } + } + ] +} diff --git a/app/problems/javascript/level1/js-l1-002.json b/app/problems/javascript/level1/js-l1-002.json new file mode 100644 index 0000000..5cbd09e --- /dev/null +++ b/app/problems/javascript/level1/js-l1-002.json @@ -0,0 +1,27 @@ +{ + "id": "js-l1-002", + "codeLanguage": "javascript", + "level": 1, + "code": [ + "function countDown(num) {", + " let i = num;", + " while (i > 0) {", + " console.log(i);", + " }", + " return 'Done';", + "}" + ], + "issues": [ + { + "id": "js-l1-002-1", + "lines": [3, 4, 5], + "type": "bug", + "severity": "critical", + "score": 4, + "description": { + "ja": "whileループ内でiをデクリメントしていないため、無限ループが発生します", + "en": "Infinite loop occurs because 'i' is never decremented inside the while loop" + } + } + ] +} diff --git a/app/problems/javascript/level1/js-l1-003.json b/app/problems/javascript/level1/js-l1-003.json new file mode 100644 index 0000000..3938284 --- /dev/null +++ b/app/problems/javascript/level1/js-l1-003.json @@ -0,0 +1,26 @@ +{ + "id": "js-l1-003", + "codeLanguage": "javascript", + "level": 1, + "code": [ + "function formatDate(date) {", + " const year = date.getFullYear();", + " const month = date.getMonth();", + " const day = date.getDate();", + " return `${year}/${month}/${day}`;", + "}" + ], + "issues": [ + { + "id": "js-l1-003-1", + "lines": [3, 5], + "type": "bug", + "severity": "normal", + "score": 4, + "description": { + "ja": "getMonth()は0-11の値を返すため、月の表示が1ヶ月ずれます。+1する必要があります", + "en": "getMonth() returns 0-11, so the month display will be off by one. Need to add +1" + } + } + ] +} diff --git a/app/problems/javascript/level2/js-l2-001.json b/app/problems/javascript/level2/js-l2-001.json new file mode 100644 index 0000000..b717e98 --- /dev/null +++ b/app/problems/javascript/level2/js-l2-001.json @@ -0,0 +1,37 @@ +{ + "id": "js-l2-001", + "codeLanguage": "javascript", + "level": 2, + "code": [ + "class Calculator {", + " constructor(value) {", + " this.value = value;", + " }", + "", + " add(n) {", + " this.value += n;", + " return this;", + " }", + "", + " getValue() {", + " return this.value;", + " }", + "}", + "", + "const calc = new Calculator(10);", + "calc.add(5).subtract(3).getValue();" + ], + "issues": [ + { + "id": "js-l2-001-1", + "lines": [17], + "type": "bug", + "severity": "critical", + "score": 4, + "description": { + "ja": "subtractメソッドが定義されていないため、実行時にTypeErrorが発生します", + "en": "The 'subtract' method is not defined, causing a TypeError at runtime" + } + } + ] +} diff --git a/app/problems/javascript/level2/js-l2-002.json b/app/problems/javascript/level2/js-l2-002.json new file mode 100644 index 0000000..2f3bea9 --- /dev/null +++ b/app/problems/javascript/level2/js-l2-002.json @@ -0,0 +1,38 @@ +{ + "id": "js-l2-002", + "codeLanguage": "javascript", + "level": 2, + "code": [ + "function removeDuplicates(items) {", + " const uniqueItems = new Set(items);", + " return uniqueItems[0];", + "}", + "", + "const result = removeDuplicates([1, 2, 2, 3, 3, 4]);", + "console.log(result);" + ], + "issues": [ + { + "id": "js-l2-002-1", + "lines": [3], + "type": "bug", + "severity": "critical", + "score": 4, + "description": { + "ja": "Setはインデックスアクセスできません。Array.from()やスプレッド構文を使う必要があります", + "en": "Set cannot be accessed by index. Need to use Array.from() or spread syntax" + } + }, + { + "id": "js-l2-002-2", + "lines": [3], + "type": "design", + "severity": "normal", + "score": 2, + "description": { + "ja": "関数名はremoveDuplicatesなのに最初の要素だけを返すのは意図が不明確です", + "en": "The function name suggests removing duplicates, but it only returns the first element" + } + } + ] +} diff --git a/app/problems/javascript/level2/js-l2-003.json b/app/problems/javascript/level2/js-l2-003.json new file mode 100644 index 0000000..f2ca370 --- /dev/null +++ b/app/problems/javascript/level2/js-l2-003.json @@ -0,0 +1,41 @@ +{ + "id": "js-l2-003", + "codeLanguage": "javascript", + "level": 2, + "code": [ + "function calculateTotal(price, quantity) {", + " const tax = 0.1;", + " const subtotal = price * quantity;", + " const total = subtotal + subtotal * tax;", + " return total.toFixed(2);", + "}", + "", + "const userInput = document.getElementById('quantity').value;", + "const result = calculateTotal(1000, userInput);", + "console.log(`合計: ${result}円`);" + ], + "issues": [ + { + "id": "js-l2-003-1", + "lines": [8, 9], + "type": "bug", + "severity": "normal", + "score": 4, + "description": { + "ja": "input要素のvalueは文字列なので、数値演算の前にNumber()やparseInt()で変換が必要です", + "en": "The value property of input element is a string, needs conversion with Number() or parseInt() before arithmetic operations" + } + }, + { + "id": "js-l2-003-2", + "lines": [9], + "type": "design", + "severity": "minor", + "score": 2, + "description": { + "ja": "入力値の妥当性チェック(NaN、負数、小数など)が行われていません", + "en": "No validation for input value (NaN, negative numbers, decimals, etc.)" + } + } + ] +} diff --git a/app/problems/javascript/level3/js-l3-001.json b/app/problems/javascript/level3/js-l3-001.json new file mode 100644 index 0000000..20108eb --- /dev/null +++ b/app/problems/javascript/level3/js-l3-001.json @@ -0,0 +1,45 @@ +{ + "id": "js-l3-001", + "codeLanguage": "javascript", + "level": 3, + "code": [ + "function updateUserSettings(currentSettings, updates) {", + " const entries = Object.entries(updates);", + " ", + " for (let i = 0; i < entries.length; i++) {", + " const key = entries[i][0];", + " const value = entries[i][1];", + " currentSettings.key = value;", + " }", + " ", + " return currentSettings;", + "}", + "", + "const settings = { theme: 'dark', language: 'ja' };", + "updateUserSettings(settings, { theme: 'light' });" + ], + "issues": [ + { + "id": "js-l3-001-1", + "lines": [7], + "type": "bug", + "severity": "critical", + "score": 4, + "description": { + "ja": "ドット記法でkeyを使うと文字列'key'がプロパティ名になります。ブラケット記法[key]を使う必要があります", + "en": "Using dot notation with 'key' creates a property named 'key'. Should use bracket notation [key]" + } + }, + { + "id": "js-l3-001-2", + "lines": [4, 5, 6, 7, 8], + "type": "design", + "severity": "minor", + "score": 2, + "description": { + "ja": "for...ofループを使えばより簡潔に書けます: for (const [key, value] of entries)", + "en": "Can be written more concisely using for...of loop: for (const [key, value] of entries)" + } + } + ] +} diff --git a/app/problems/javascript/level3/js-l3-002.json b/app/problems/javascript/level3/js-l3-002.json new file mode 100644 index 0000000..1aec8a6 --- /dev/null +++ b/app/problems/javascript/level3/js-l3-002.json @@ -0,0 +1,64 @@ +{ + "id": "js-l3-002", + "codeLanguage": "javascript", + "level": 3, + "code": [ + "class User {", + " constructor(id, name) {", + " this.id = id;", + " this.name = name;", + " }", + "", + " static findById(users, id) {", + " return users.find(user => user.id === id);", + " }", + "", + " findByName(name) {", + " return this.find(user => user.name === name);", + " }", + "}", + "", + "const users = [", + " new User(1, 'Alice'),", + " new User(2, 'Bob')", + "];", + "", + "const user = User.findById(users, 1);", + "const result = user.findByName('Bob');" + ], + "issues": [ + { + "id": "js-l3-002-1", + "lines": [12], + "type": "bug", + "severity": "critical", + "score": 4, + "description": { + "ja": "インスタンスメソッド内でthis.find()を呼んでいますが、thisはUserインスタンスで配列ではありません", + "en": "Calling this.find() in instance method, but 'this' is a User instance, not an array" + } + }, + { + "id": "js-l3-002-2", + "lines": [22], + "type": "bug", + "severity": "critical", + "score": 4, + "description": { + "ja": "findByNameはインスタンスメソッドですが、配列を受け取るように設計されていません。静的メソッドにすべきです", + "en": "findByName is an instance method but not designed to receive an array. Should be a static method" + } + }, + { + "id": "js-l3-002-3", + "lines": [11, 12, 13], + "type": "design", + "severity": "normal", + "score": 2, + "description": { + "ja": "findByNameメソッドの設計が不適切です。静的メソッドとして定義し、配列を引数に取るべきです", + "en": "Poor design of findByName method. Should be defined as a static method and take an array as parameter" + } + } + ] +}