irlxleet 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/irlxleet.js +27 -0
- package/package.json +30 -0
- package/pypi-wrapper/README.md +12 -0
- package/pypi-wrapper/dist/irlxleet-1.0.0-py3-none-any.whl +0 -0
- package/pypi-wrapper/dist/irlxleet-1.0.0.tar.gz +0 -0
- package/pypi-wrapper/irlxleet/__init__.py +1 -0
- package/pypi-wrapper/irlxleet/cli.py +14 -0
- package/pypi-wrapper/irlxleet.egg-info/PKG-INFO +31 -0
- package/pypi-wrapper/irlxleet.egg-info/SOURCES.txt +9 -0
- package/pypi-wrapper/irlxleet.egg-info/dependency_links.txt +1 -0
- package/pypi-wrapper/irlxleet.egg-info/entry_points.txt +2 -0
- package/pypi-wrapper/irlxleet.egg-info/top_level.txt +1 -0
- package/pypi-wrapper/setup.py +26 -0
- package/src/ai/breaker.js +49 -0
- package/src/ai/coach.js +35 -0
- package/src/ai/prompts.js +44 -0
- package/src/commands/config.js +47 -0
- package/src/commands/daily.js +15 -0
- package/src/commands/solve.js +115 -0
- package/src/commands/stats.js +53 -0
- package/src/commands/unbreakable.js +110 -0
- package/src/core/engine.js +73 -0
- package/src/core/judge.js +53 -0
- package/src/core/scorer.js +43 -0
- package/src/core/tracker.js +42 -0
- package/src/problems/categories/arrays.json +29 -0
- package/src/problems/loader.js +60 -0
- package/src/store/db.js +58 -0
- package/src/store/migrations.js +36 -0
- package/src/ui/banner.js +17 -0
- package/src/ui/progress.js +79 -0
- package/src/ui/table.js +26 -0
- package/src/ui/theme.js +24 -0
- package/templates/solution.js.tmpl +17 -0
- package/templates/solution.py.tmpl +15 -0
package/bin/irlxleet.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { showBanner } from '../src/ui/banner.js';
|
|
4
|
+
import { registerSolveCommand } from '../src/commands/solve.js';
|
|
5
|
+
import { registerConfigCommand } from '../src/commands/config.js';
|
|
6
|
+
import { registerStatsCommand } from '../src/commands/stats.js';
|
|
7
|
+
import { registerUnbreakableCommand } from '../src/commands/unbreakable.js';
|
|
8
|
+
import { registerDailyCommand } from '../src/commands/daily.js';
|
|
9
|
+
|
|
10
|
+
const program = new Command();
|
|
11
|
+
program.name('irlxleet').description('Code. Break. Become Unbreakable.').version('1.0.0');
|
|
12
|
+
|
|
13
|
+
showBanner();
|
|
14
|
+
|
|
15
|
+
registerSolveCommand(program);
|
|
16
|
+
registerConfigCommand(program);
|
|
17
|
+
registerStatsCommand(program);
|
|
18
|
+
registerUnbreakableCommand(program);
|
|
19
|
+
registerDailyCommand(program);
|
|
20
|
+
|
|
21
|
+
// Default fallback for unhandled
|
|
22
|
+
program.on('command:*', () => {
|
|
23
|
+
console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "irlxleet",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Code. Break. Become Unbreakable. — Savage FAANG DSA Prep CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"irlxleet": "./bin/irlxleet.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/irlxleet.js",
|
|
11
|
+
"dev": "node --watch bin/irlxleet.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["dsa", "leetcode", "faang", "cli", "interview-prep"],
|
|
14
|
+
"author": "irlxleet",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"commander": "^13.1.0",
|
|
18
|
+
"inquirer": "^12.6.0",
|
|
19
|
+
"chalk": "^5.4.1",
|
|
20
|
+
"ora": "^8.2.0",
|
|
21
|
+
"figlet": "^1.8.0",
|
|
22
|
+
"boxen": "^8.0.1",
|
|
23
|
+
"cli-table3": "^0.6.5",
|
|
24
|
+
"@google/generative-ai": "^0.24.0",
|
|
25
|
+
"conf": "^13.1.0",
|
|
26
|
+
"grammy": "^1.35.0",
|
|
27
|
+
"gradient-string": "^3.0.0",
|
|
28
|
+
"cli-progress": "^3.12.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# irlxleet module
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
def main():
|
|
5
|
+
try:
|
|
6
|
+
# Calls the JS CLI via npx
|
|
7
|
+
subprocess.run(["npx", "-y", "irlxleet@latest"] + sys.argv[1:])
|
|
8
|
+
except FileNotFoundError:
|
|
9
|
+
print("🔥 ERROR: Node.js is required to run irlxleet.")
|
|
10
|
+
print("Please install Node.js from https://nodejs.org/")
|
|
11
|
+
sys.exit(1)
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
main()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: irlxleet
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Code. Break. Become Unbreakable. — Savage FAANG DSA Prep CLI
|
|
5
|
+
Home-page: https://github.com/irlxleet/irlxleet
|
|
6
|
+
Author: irlxleet
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.6
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: classifier
|
|
14
|
+
Dynamic: description
|
|
15
|
+
Dynamic: description-content-type
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: requires-python
|
|
18
|
+
Dynamic: summary
|
|
19
|
+
|
|
20
|
+
# irlxleet
|
|
21
|
+
|
|
22
|
+
Code. Break. Become Unbreakable. — Savage FAANG DSA Prep CLI
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
This package requires **Node.js** to run because the core engine is built in JavaScript.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install irlxleet
|
|
30
|
+
irlxleet --help
|
|
31
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
irlxleet
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name='irlxleet',
|
|
8
|
+
version='1.0.0',
|
|
9
|
+
description='Code. Break. Become Unbreakable. — Savage FAANG DSA Prep CLI',
|
|
10
|
+
long_description=long_description,
|
|
11
|
+
long_description_content_type="text/markdown",
|
|
12
|
+
author='irlxleet',
|
|
13
|
+
url='https://github.com/irlxleet/irlxleet',
|
|
14
|
+
packages=find_packages(),
|
|
15
|
+
entry_points={
|
|
16
|
+
'console_scripts': [
|
|
17
|
+
'irlxleet=irlxleet.cli:main',
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
classifiers=[
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
],
|
|
25
|
+
python_requires='>=3.6',
|
|
26
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
2
|
+
|
|
3
|
+
export class CodeBreaker {
|
|
4
|
+
constructor(apiKey) {
|
|
5
|
+
this.genAI = new GoogleGenerativeAI(apiKey);
|
|
6
|
+
this.model = this.genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async generateEdgeCases(problem, userCode, level) {
|
|
10
|
+
const prompt = `
|
|
11
|
+
You are an adversarial AI tester for a DSA problem: ${problem.title}.
|
|
12
|
+
The user's code:
|
|
13
|
+
${userCode}
|
|
14
|
+
|
|
15
|
+
Level is ${level}.
|
|
16
|
+
Level 1: Generate 3 tricky edge cases (e.g. empty arrays, single elements, negative numbers).
|
|
17
|
+
Level 2: Generate 3 adversarial inputs targeting time complexity (e.g. very large inputs, worst-case scenarios).
|
|
18
|
+
|
|
19
|
+
Return ONLY a valid JSON array of test cases. Format: [{"input": [arg1, arg2], "expected": expectedResult}]
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const result = await this.model.generateContent(prompt);
|
|
24
|
+
const text = result.response.text().replace(/```json/g, '').replace(/```/g, '').trim();
|
|
25
|
+
return JSON.parse(text);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async mutateCode(problem, userCode) {
|
|
32
|
+
const prompt = `
|
|
33
|
+
You are the DOOM AI. You will break the user's working code by introducing 3 subtle bugs.
|
|
34
|
+
Do not make it obviously fail syntax. Change operators (e.g. < to <=), swap variables, off-by-one errors.
|
|
35
|
+
|
|
36
|
+
Problem: ${problem.title}
|
|
37
|
+
Code:
|
|
38
|
+
${userCode}
|
|
39
|
+
|
|
40
|
+
Return ONLY the mutated code as plain text. No markdown. No explanations.
|
|
41
|
+
`;
|
|
42
|
+
try {
|
|
43
|
+
const result = await this.model.generateContent(prompt);
|
|
44
|
+
return result.response.text().replace(/```javascript/g, '').replace(/```/g, '').trim();
|
|
45
|
+
} catch (e) {
|
|
46
|
+
return userCode;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/ai/coach.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
2
|
+
import { getHintPrompt, getExplainPrompt, getGussaPrompt } from './prompts.js';
|
|
3
|
+
|
|
4
|
+
export class DoomCoach {
|
|
5
|
+
constructor(apiKey) {
|
|
6
|
+
this.genAI = new GoogleGenerativeAI(apiKey);
|
|
7
|
+
this.model = this.genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async getHelp(problem, userCode, testResults, mistakeCount) {
|
|
11
|
+
let prompt = "";
|
|
12
|
+
let level = "hint";
|
|
13
|
+
let showAnswer = false;
|
|
14
|
+
|
|
15
|
+
if (mistakeCount === 1) {
|
|
16
|
+
prompt = getHintPrompt(problem, userCode, testResults);
|
|
17
|
+
level = "hint";
|
|
18
|
+
} else if (mistakeCount === 2) {
|
|
19
|
+
prompt = getExplainPrompt(problem, userCode, testResults);
|
|
20
|
+
level = "explain";
|
|
21
|
+
} else {
|
|
22
|
+
prompt = getGussaPrompt(problem, userCode, testResults);
|
|
23
|
+
level = "gussa";
|
|
24
|
+
showAnswer = true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const result = await this.model.generateContent(prompt);
|
|
29
|
+
const message = result.response.text();
|
|
30
|
+
return { level, message, showAnswer };
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return { level: 'error', message: "DOOM Coach is asleep (API Error): " + error.message, showAnswer: false };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export function getHintPrompt(problem, userCode, testResults) {
|
|
2
|
+
return `
|
|
3
|
+
You are the AI DOOM Coach for a FAANG DSA Prep CLI.
|
|
4
|
+
The user is attempting the problem: ${problem.title}.
|
|
5
|
+
They got it wrong on their first try.
|
|
6
|
+
|
|
7
|
+
Problem Description:
|
|
8
|
+
${problem.description}
|
|
9
|
+
|
|
10
|
+
User's Code:
|
|
11
|
+
${userCode}
|
|
12
|
+
|
|
13
|
+
Test Results:
|
|
14
|
+
${JSON.stringify(testResults, null, 2)}
|
|
15
|
+
|
|
16
|
+
Give a one-line subtle hint to nudge them in the right direction. Do NOT reveal the full solution or write code for them. Be encouraging but firm.
|
|
17
|
+
`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getExplainPrompt(problem, userCode, testResults) {
|
|
21
|
+
return `
|
|
22
|
+
You are the AI DOOM Coach.
|
|
23
|
+
The user failed ${problem.title} twice.
|
|
24
|
+
Explain the core concept or algorithm they are missing based on their code.
|
|
25
|
+
You can show pseudo-code, but do not give the exact final code in their language.
|
|
26
|
+
Be like a patient senior engineer guiding a junior.
|
|
27
|
+
|
|
28
|
+
User Code:
|
|
29
|
+
${userCode}
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getGussaPrompt(problem, userCode, testResults) {
|
|
34
|
+
return `
|
|
35
|
+
You are the AI DOOM Coach.
|
|
36
|
+
The user has failed ${problem.title} THREE times or more with similar mistakes.
|
|
37
|
+
Go into GUSSA MODE (Angry Mode). Roast the user in Hinglish (a mix of Hindi and English written in Latin script).
|
|
38
|
+
Be savage but educational. Tell them exactly why their code is garbage, then show them the correct logic.
|
|
39
|
+
Make it funny and aggressive like a frustrated tech lead. "Bhai kya kar raha hai? Sliding window use kar na!"
|
|
40
|
+
|
|
41
|
+
User Code:
|
|
42
|
+
${userCode}
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import Conf from 'conf';
|
|
3
|
+
import { theme } from '../ui/theme.js';
|
|
4
|
+
|
|
5
|
+
const config = new Conf({ projectName: 'irlxleet' });
|
|
6
|
+
|
|
7
|
+
export function registerConfigCommand(program) {
|
|
8
|
+
program
|
|
9
|
+
.command('config')
|
|
10
|
+
.description('Setup API keys and preferences')
|
|
11
|
+
.option('--reset', 'Reset configuration')
|
|
12
|
+
.action(async (options) => {
|
|
13
|
+
if (options.reset) {
|
|
14
|
+
config.clear();
|
|
15
|
+
console.log(theme.success('Configuration reset successfully.'));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log(theme.fire('🔥 Welcome to irlxleet Config 🔥\n'));
|
|
20
|
+
|
|
21
|
+
const currentGemini = config.get('geminiApiKey', '');
|
|
22
|
+
|
|
23
|
+
const answers = await inquirer.prompt([
|
|
24
|
+
{
|
|
25
|
+
type: 'input',
|
|
26
|
+
name: 'geminiApiKey',
|
|
27
|
+
message: 'Enter your Google Gemini API Key:',
|
|
28
|
+
default: currentGemini,
|
|
29
|
+
validate: input => input.length > 10 ? true : 'Please enter a valid API key'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'list',
|
|
33
|
+
name: 'language',
|
|
34
|
+
message: 'Preferred programming language?',
|
|
35
|
+
choices: ['javascript', 'python'],
|
|
36
|
+
default: config.get('language', 'javascript')
|
|
37
|
+
}
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
config.set(answers);
|
|
41
|
+
console.log('\n' + theme.success('✅ Setup complete! You are ready to face the DOOM Coach.'));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getConfig() {
|
|
46
|
+
return config;
|
|
47
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getRandomProblem } from '../problems/loader.js';
|
|
2
|
+
// Re-uses solve flow conceptually, but wraps it in daily
|
|
3
|
+
// To keep things simple for MVP, we just alias to solve with a random problem today
|
|
4
|
+
import { registerSolveCommand } from './solve.js';
|
|
5
|
+
|
|
6
|
+
export function registerDailyCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('daily')
|
|
9
|
+
.description('Play the daily challenge')
|
|
10
|
+
.action(() => {
|
|
11
|
+
console.log("Daily challenge starts here! (Redirecting to random solve...)");
|
|
12
|
+
// We'll just hint them to use solve --random for now
|
|
13
|
+
console.log("For MVP, run `irlxleet solve` and pick a problem.");
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import boxen from 'boxen';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
7
|
+
import { theme } from '../ui/theme.js';
|
|
8
|
+
import { renderTestResults } from '../ui/table.js';
|
|
9
|
+
import { runTests } from '../core/judge.js';
|
|
10
|
+
import { recordSolve } from '../core/tracker.js';
|
|
11
|
+
import { incrementMistake, getMistakeCount, resetMistake } from '../store/db.js';
|
|
12
|
+
import { calculateScore } from '../core/scorer.js';
|
|
13
|
+
import { DoomCoach } from '../ai/coach.js';
|
|
14
|
+
import { getConfig } from './config.js';
|
|
15
|
+
import { getCategories, getProblemsByCategory } from '../problems/loader.js';
|
|
16
|
+
|
|
17
|
+
export function registerSolveCommand(program) {
|
|
18
|
+
program
|
|
19
|
+
.command('solve')
|
|
20
|
+
.description('Pick a problem and solve it')
|
|
21
|
+
.action(async () => {
|
|
22
|
+
const config = getConfig();
|
|
23
|
+
const apiKey = config.get('geminiApiKey');
|
|
24
|
+
|
|
25
|
+
if (!apiKey) {
|
|
26
|
+
console.log(theme.error('No Gemini API Key found. Run `irlxleet config` first.'));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const categories = getCategories();
|
|
31
|
+
const { category } = await inquirer.prompt([{
|
|
32
|
+
type: 'list', name: 'category', message: 'Choose a category:', choices: categories
|
|
33
|
+
}]);
|
|
34
|
+
|
|
35
|
+
const problems = getProblemsByCategory(category);
|
|
36
|
+
const choices = problems.map(p => ({
|
|
37
|
+
name: `${p.title} [${p.difficulty.toUpperCase()}]`,
|
|
38
|
+
value: p
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
const { problem } = await inquirer.prompt([{
|
|
42
|
+
type: 'list', name: 'problem', message: 'Choose a problem:', choices
|
|
43
|
+
}]);
|
|
44
|
+
|
|
45
|
+
console.log('\n' + boxen(
|
|
46
|
+
`${theme.fire(problem.title)} [${problem.difficulty}]\n\n${problem.description}\n\nConstraints:\n${problem.constraints.join('\n')}`,
|
|
47
|
+
theme.boxStyles.problem
|
|
48
|
+
));
|
|
49
|
+
|
|
50
|
+
const solutionsDir = join(homedir(), '.irlxleet', 'solutions');
|
|
51
|
+
if (!existsSync(solutionsDir)) {
|
|
52
|
+
import('fs').then(fs => fs.mkdirSync(solutionsDir, { recursive: true }));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const filePath = join(solutionsDir, `${problem.id}.js`);
|
|
56
|
+
|
|
57
|
+
if (!existsSync(filePath)) {
|
|
58
|
+
const templatePath = join(process.cwd(), 'templates', 'solution.js.tmpl');
|
|
59
|
+
const template = readFileSync(templatePath, 'utf-8');
|
|
60
|
+
const content = template
|
|
61
|
+
.replace('{{title}}', problem.title)
|
|
62
|
+
.replace('{{difficulty}}', problem.difficulty)
|
|
63
|
+
.replace('{{category}}', problem.category)
|
|
64
|
+
.replace('{{description}}', problem.description.replace(/\n/g, '\n * '))
|
|
65
|
+
.replace('{{constraints}}', problem.constraints.map(c => ` * - ${c}`).join('\n'))
|
|
66
|
+
.replace(/\{\{functionName\}\}/g, problem.functionName)
|
|
67
|
+
.replace('{{params}}', problem.parameterNames.join(', '));
|
|
68
|
+
|
|
69
|
+
writeFileSync(filePath, content);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(theme.info(`\nSolution file ready at: ${filePath}`));
|
|
73
|
+
console.log(theme.info(`Open it in your editor, write your code, then press Enter here to test it.`));
|
|
74
|
+
|
|
75
|
+
let solved = false;
|
|
76
|
+
let attempts = 0;
|
|
77
|
+
const startTime = Date.now();
|
|
78
|
+
const coach = new DoomCoach(apiKey);
|
|
79
|
+
|
|
80
|
+
while (!solved) {
|
|
81
|
+
await inquirer.prompt([{ type: 'input', name: 'wait', message: 'Press Enter to submit code...' }]);
|
|
82
|
+
|
|
83
|
+
attempts++;
|
|
84
|
+
const userCode = readFileSync(filePath, 'utf-8');
|
|
85
|
+
|
|
86
|
+
const spinner = ora('Judging your code...').start();
|
|
87
|
+
const results = await runTests(userCode, 'javascript', problem.testCases, problem.functionName, problem.parameterNames);
|
|
88
|
+
spinner.stop();
|
|
89
|
+
|
|
90
|
+
renderTestResults(results.results);
|
|
91
|
+
|
|
92
|
+
if (results.passed) {
|
|
93
|
+
const timeMs = Date.now() - startTime;
|
|
94
|
+
const score = calculateScore(problem.difficulty, attempts, timeMs, attempts === 1);
|
|
95
|
+
|
|
96
|
+
console.log('\n' + boxen(theme.success(`🎉 ACCEPTED! You earned ${score} points!`), { padding: 1, borderColor: 'green' }));
|
|
97
|
+
recordSolve(problem.id, userCode, 'javascript', true, results.totalTests, results.totalPassed, attempts, timeMs);
|
|
98
|
+
resetMistake(problem.id);
|
|
99
|
+
solved = true;
|
|
100
|
+
} else {
|
|
101
|
+
incrementMistake(problem.id);
|
|
102
|
+
const mistakes = getMistakeCount(problem.id);
|
|
103
|
+
|
|
104
|
+
const coachSpinner = ora('DOOM Coach is analyzing your failure...').start();
|
|
105
|
+
const help = await coach.getHelp(problem, userCode, results.results, mistakes);
|
|
106
|
+
coachSpinner.stop();
|
|
107
|
+
|
|
108
|
+
const boxStyle = theme.boxStyles[help.level] || theme.boxStyles.hint;
|
|
109
|
+
const title = help.level === 'gussa' ? '🔥 DOOM COACH SAYS:' : '💡 DOOM COACH HINT:';
|
|
110
|
+
|
|
111
|
+
console.log('\n' + boxen(`${theme.doom(title)}\n\n${help.message}`, boxStyle));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { theme } from '../ui/theme.js';
|
|
2
|
+
import boxen from 'boxen';
|
|
3
|
+
import { getDb } from '../store/db.js';
|
|
4
|
+
import { getCurrentStreak } from '../core/tracker.js';
|
|
5
|
+
import { getDoomRank } from '../core/scorer.js';
|
|
6
|
+
|
|
7
|
+
export function registerStatsCommand(program) {
|
|
8
|
+
program
|
|
9
|
+
.command('stats')
|
|
10
|
+
.description('View your DOOM stats and rank')
|
|
11
|
+
.action(() => {
|
|
12
|
+
const db = getDb();
|
|
13
|
+
const solutions = db.solutions || [];
|
|
14
|
+
const totalSolved = solutions.filter(s => s.passed === 1).length;
|
|
15
|
+
|
|
16
|
+
let totalTime = 0;
|
|
17
|
+
let totalAttempts = 0;
|
|
18
|
+
let totalScore = 0;
|
|
19
|
+
let firstTries = 0;
|
|
20
|
+
|
|
21
|
+
solutions.forEach(s => {
|
|
22
|
+
totalTime += s.time_ms;
|
|
23
|
+
totalAttempts += s.attempts;
|
|
24
|
+
if (s.passed === 1) {
|
|
25
|
+
let base = 10;
|
|
26
|
+
totalScore += base;
|
|
27
|
+
if (s.attempts === 1) firstTries++;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const streak = getCurrentStreak();
|
|
32
|
+
const rankInfo = getDoomRank(totalScore);
|
|
33
|
+
|
|
34
|
+
const avgTime = totalSolved > 0 ? (totalTime / totalSolved / 60000).toFixed(1) : 0;
|
|
35
|
+
const firstTryRate = totalSolved > 0 ? ((firstTries / totalSolved) * 100).toFixed(0) : 0;
|
|
36
|
+
|
|
37
|
+
const statsBox = `
|
|
38
|
+
🔥 Streak: ${streak} days
|
|
39
|
+
💀 DOOM Rank: ${rankInfo.badge} ${rankInfo.name}
|
|
40
|
+
📊 Solved: ${totalSolved} problems
|
|
41
|
+
⚡ Avg Time: ${avgTime} min
|
|
42
|
+
🎯 1st Try Rate: ${firstTryRate}%
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
console.log('\n' + boxen(statsBox, {
|
|
46
|
+
title: theme.doom(' 💀 DOOM STATS 💀 '),
|
|
47
|
+
titleAlignment: 'center',
|
|
48
|
+
padding: 1,
|
|
49
|
+
borderStyle: 'double',
|
|
50
|
+
borderColor: 'red'
|
|
51
|
+
}));
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import boxen from 'boxen';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
7
|
+
import { theme } from '../ui/theme.js';
|
|
8
|
+
import { getConfig } from './config.js';
|
|
9
|
+
import { CodeBreaker } from '../ai/breaker.js';
|
|
10
|
+
import { runTests } from '../core/judge.js';
|
|
11
|
+
import { renderTestResults } from '../ui/table.js';
|
|
12
|
+
import { getDb } from '../store/db.js';
|
|
13
|
+
import { getProblemById } from '../problems/loader.js';
|
|
14
|
+
|
|
15
|
+
export function registerUnbreakableCommand(program) {
|
|
16
|
+
program
|
|
17
|
+
.command('unbreakable')
|
|
18
|
+
.description('Enter Unbreakable Mode (AI destroys your code)')
|
|
19
|
+
.action(async () => {
|
|
20
|
+
const config = getConfig();
|
|
21
|
+
const apiKey = config.get('geminiApiKey');
|
|
22
|
+
|
|
23
|
+
if (!apiKey) {
|
|
24
|
+
console.log(theme.error('No Gemini API Key found. Run `irlxleet config` first.'));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const db = getDb();
|
|
29
|
+
const solvedSolutions = (db.solutions || []).filter(s => s.passed === 1);
|
|
30
|
+
|
|
31
|
+
// Get unique solved problem IDs
|
|
32
|
+
const uniqueSolvedIds = [...new Set(solvedSolutions.map(s => s.problem_id))];
|
|
33
|
+
|
|
34
|
+
if (uniqueSolvedIds.length === 0) {
|
|
35
|
+
console.log(theme.warning('You need to solve at least one problem first!'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const choices = uniqueSolvedIds.map(id => {
|
|
40
|
+
const p = getProblemById(id);
|
|
41
|
+
return { name: p.title, value: p };
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const { problem } = await inquirer.prompt([{
|
|
45
|
+
type: 'list', name: 'problem', message: 'Choose a solved problem to defend:', choices
|
|
46
|
+
}]);
|
|
47
|
+
|
|
48
|
+
const { level } = await inquirer.prompt([{
|
|
49
|
+
type: 'list', name: 'level', message: 'Choose your DOOM level:',
|
|
50
|
+
choices: [
|
|
51
|
+
{ name: '🟢 Level 1: Crack (Edge cases)', value: 1 },
|
|
52
|
+
{ name: '🟡 Level 2: Smash (Performance adversarial)', value: 2 },
|
|
53
|
+
{ name: '🔴 Level 3: DOOM (Code mutation)', value: 3 }
|
|
54
|
+
]
|
|
55
|
+
}]);
|
|
56
|
+
|
|
57
|
+
const filePath = join(homedir(), '.irlxleet', 'solutions', `${problem.id}.js`);
|
|
58
|
+
const userCode = readFileSync(filePath, 'utf-8');
|
|
59
|
+
const breaker = new CodeBreaker(apiKey);
|
|
60
|
+
|
|
61
|
+
console.log('\n' + boxen(theme.doom(` ☠️ UNBREAKABLE MODE — LEVEL ${level} ☠️ `), { padding: 1, borderColor: 'red' }));
|
|
62
|
+
|
|
63
|
+
if (level === 1 || level === 2) {
|
|
64
|
+
const spinner = ora('AI is generating adversarial test cases...').start();
|
|
65
|
+
const edgeCases = await breaker.generateEdgeCases(problem, userCode, level);
|
|
66
|
+
spinner.stop();
|
|
67
|
+
|
|
68
|
+
if (edgeCases.length === 0) {
|
|
69
|
+
console.log(theme.warning('AI failed to find edge cases. You win this round by default!'));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(theme.info(`\nAI found ${edgeCases.length} adversarial cases. Running your code...`));
|
|
74
|
+
|
|
75
|
+
const results = await runTests(userCode, 'javascript', { visible: edgeCases, hidden: [] }, problem.functionName, problem.parameterNames);
|
|
76
|
+
renderTestResults(results.results);
|
|
77
|
+
|
|
78
|
+
if (results.passed) {
|
|
79
|
+
console.log(theme.success('\n🛡️ UNBREAKABLE! Your code survived.'));
|
|
80
|
+
} else {
|
|
81
|
+
console.log(theme.error('\n💥 YOUR CODE BROKE. Fix it and try again.'));
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
// Level 3 Code Mutation
|
|
85
|
+
const spinner = ora('AI is mutating your code...').start();
|
|
86
|
+
const mutatedCode = await breaker.mutateCode(problem, userCode);
|
|
87
|
+
spinner.stop();
|
|
88
|
+
|
|
89
|
+
const mutatedPath = join(homedir(), '.irlxleet', 'solutions', `${problem.id}.mutated.js`);
|
|
90
|
+
writeFileSync(mutatedPath, mutatedCode);
|
|
91
|
+
|
|
92
|
+
console.log(theme.error(`\nAI injected 3 bugs into your code.`));
|
|
93
|
+
console.log(`Mutated code saved to: ${mutatedPath}`);
|
|
94
|
+
console.log(theme.info('You have 5 minutes to find and fix them.'));
|
|
95
|
+
|
|
96
|
+
// Wait for user to fix
|
|
97
|
+
await inquirer.prompt([{ type: 'input', name: 'wait', message: 'Press Enter when you have fixed the bugs...' }]);
|
|
98
|
+
|
|
99
|
+
const fixedCode = readFileSync(mutatedPath, 'utf-8');
|
|
100
|
+
const results = await runTests(fixedCode, 'javascript', problem.testCases, problem.functionName, problem.parameterNames);
|
|
101
|
+
renderTestResults(results.results);
|
|
102
|
+
|
|
103
|
+
if (results.passed) {
|
|
104
|
+
console.log(theme.success('\n🏆 YOU BEAT DOOM MODE! Respect.'));
|
|
105
|
+
} else {
|
|
106
|
+
console.log(theme.error('\n💀 You failed to fix all bugs. The AI wins.'));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { execFile } from 'child_process';
|
|
2
|
+
import { tmpdir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { writeFile, unlink } from 'fs/promises';
|
|
5
|
+
import { randomBytes } from 'crypto';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Executes code in a sandbox (child_process).
|
|
9
|
+
* @param {string} userCode The user's solution code.
|
|
10
|
+
* @param {string} language 'javascript' or 'python'
|
|
11
|
+
* @param {string} functionName The name of the function to call
|
|
12
|
+
* @param {Array} inputArgs Array of arguments to pass to the function
|
|
13
|
+
* @param {number} timeoutMs Execution timeout in ms
|
|
14
|
+
*/
|
|
15
|
+
export async function executeCode(userCode, language, functionName, inputArgs, timeoutMs = 10000) {
|
|
16
|
+
const tempId = randomBytes(16).toString('hex');
|
|
17
|
+
|
|
18
|
+
if (language === 'javascript') {
|
|
19
|
+
const fileName = join(tmpdir(), `irlxleet_${tempId}.mjs`);
|
|
20
|
+
|
|
21
|
+
// Create a wrapper to parse inputs, call the function, and output JSON
|
|
22
|
+
const wrapper = `
|
|
23
|
+
${userCode.replace(/export default/g, 'const __exported_func =')}
|
|
24
|
+
|
|
25
|
+
const args = ${JSON.stringify(inputArgs)};
|
|
26
|
+
try {
|
|
27
|
+
const result = __exported_func(...args);
|
|
28
|
+
console.log(JSON.stringify({ success: true, result }));
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.log(JSON.stringify({ success: false, error: err.message, stack: err.stack }));
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await writeFile(fileName, wrapper);
|
|
36
|
+
const startTime = Date.now();
|
|
37
|
+
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
execFile('node', [fileName], { timeout: timeoutMs }, async (error, stdout, stderr) => {
|
|
40
|
+
const executionTime = Date.now() - startTime;
|
|
41
|
+
await unlink(fileName).catch(() => {}); // cleanup
|
|
42
|
+
|
|
43
|
+
let parsedOutput = null;
|
|
44
|
+
let executionError = null;
|
|
45
|
+
|
|
46
|
+
if (stdout) {
|
|
47
|
+
try {
|
|
48
|
+
parsedOutput = JSON.parse(stdout.trim());
|
|
49
|
+
} catch(e) {
|
|
50
|
+
// If it fails to parse, it means user code logged something else
|
|
51
|
+
executionError = 'Failed to parse output. Did you console.log something else? Stdout: ' + stdout;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
resolve({
|
|
56
|
+
stdout,
|
|
57
|
+
stderr,
|
|
58
|
+
exitCode: error ? error.code : 0,
|
|
59
|
+
timedOut: error ? error.killed : false,
|
|
60
|
+
executionTime,
|
|
61
|
+
result: parsedOutput?.result,
|
|
62
|
+
success: parsedOutput?.success ?? false,
|
|
63
|
+
executionError: parsedOutput?.error || executionError || (error ? error.message : null)
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
} catch (e) {
|
|
68
|
+
return { success: false, executionError: e.message, executionTime: 0 };
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
throw new Error(`Language ${language} not supported yet.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { executeCode } from './engine.js';
|
|
2
|
+
|
|
3
|
+
function isEqual(a, b) {
|
|
4
|
+
// basic deep equality for arrays/objects
|
|
5
|
+
if (a === b) return true;
|
|
6
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function runTests(code, language, testCases, functionName, parameterNames) {
|
|
10
|
+
const results = [];
|
|
11
|
+
let totalPassed = 0;
|
|
12
|
+
let totalTime = 0;
|
|
13
|
+
|
|
14
|
+
const allCases = [...testCases.visible, ...(testCases.hidden || [])];
|
|
15
|
+
const totalTests = allCases.length;
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < allCases.length; i++) {
|
|
18
|
+
const testCase = allCases[i];
|
|
19
|
+
const { input, expected } = testCase;
|
|
20
|
+
|
|
21
|
+
const execResult = await executeCode(code, language, functionName, input);
|
|
22
|
+
|
|
23
|
+
let passed = false;
|
|
24
|
+
let actual = execResult.result;
|
|
25
|
+
|
|
26
|
+
if (execResult.success && !execResult.timedOut && execResult.executionError === null) {
|
|
27
|
+
passed = isEqual(actual, expected);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (passed) totalPassed++;
|
|
31
|
+
totalTime += execResult.executionTime;
|
|
32
|
+
|
|
33
|
+
results.push({
|
|
34
|
+
caseNumber: i + 1,
|
|
35
|
+
input,
|
|
36
|
+
expected,
|
|
37
|
+
actual,
|
|
38
|
+
passed,
|
|
39
|
+
time: execResult.executionTime,
|
|
40
|
+
error: execResult.executionError,
|
|
41
|
+
timedOut: execResult.timedOut,
|
|
42
|
+
isHidden: i >= testCases.visible.length
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
passed: totalPassed === totalTests,
|
|
48
|
+
results,
|
|
49
|
+
totalPassed,
|
|
50
|
+
totalTests,
|
|
51
|
+
totalTime
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function calculateScore(difficulty, attempts, timeMs, passedFirstTry) {
|
|
2
|
+
let base = 10;
|
|
3
|
+
if (difficulty === 'medium') base = 25;
|
|
4
|
+
if (difficulty === 'hard') base = 50;
|
|
5
|
+
|
|
6
|
+
let score = base;
|
|
7
|
+
|
|
8
|
+
if (passedFirstTry) score *= 2;
|
|
9
|
+
|
|
10
|
+
const timeMins = timeMs / 60000;
|
|
11
|
+
let speedBonus = 1;
|
|
12
|
+
if (difficulty === 'easy' && timeMins <= 5) speedBonus = 1.5;
|
|
13
|
+
if (difficulty === 'medium' && timeMins <= 15) speedBonus = 1.5;
|
|
14
|
+
if (difficulty === 'hard' && timeMins <= 30) speedBonus = 1.5;
|
|
15
|
+
|
|
16
|
+
score *= speedBonus;
|
|
17
|
+
|
|
18
|
+
// Penalty for extra attempts (after the 1st)
|
|
19
|
+
if (attempts > 1) {
|
|
20
|
+
const penalty = Math.pow(0.8, attempts - 1);
|
|
21
|
+
score *= penalty;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return Math.round(score);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getDoomRank(totalScore) {
|
|
28
|
+
if (totalScore <= 50) return { name: 'Intern', badge: '🐣', minScore: 0 };
|
|
29
|
+
if (totalScore <= 150) return { name: 'Bug Writer', badge: '🪲', minScore: 51 };
|
|
30
|
+
if (totalScore <= 300) return { name: 'Code Monkey', badge: '🐒', minScore: 151 };
|
|
31
|
+
if (totalScore <= 500) return { name: 'Array Warrior', badge: '⚔️', minScore: 301 };
|
|
32
|
+
if (totalScore <= 800) return { name: 'Graph Slayer', badge: '🗡️', minScore: 501 };
|
|
33
|
+
if (totalScore <= 1200) return { name: 'DP Demon', badge: '👹', minScore: 801 };
|
|
34
|
+
if (totalScore <= 1800) return { name: 'FAANG Ready', badge: '🏢', minScore: 1201 };
|
|
35
|
+
return { name: 'UNBREAKABLE', badge: '💀🔥', minScore: 1801 };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getUnbreakableScore(level, edgeCasesSurvived, totalEdgeCases, timeMs) {
|
|
39
|
+
const percentage = edgeCasesSurvived / totalEdgeCases;
|
|
40
|
+
const levelMult = level === 1 ? 1 : level === 2 ? 2.5 : 5;
|
|
41
|
+
const base = 50 * levelMult * percentage;
|
|
42
|
+
return Math.round(base);
|
|
43
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getDb, saveSolution } from '../store/db.js';
|
|
2
|
+
import { writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
|
|
6
|
+
export function recordSolve(problemId, code, lang, passed, totalTests, passedTests, attempts, timeMs) {
|
|
7
|
+
saveSolution(problemId, code, lang, passed, totalTests, passedTests, attempts, timeMs);
|
|
8
|
+
|
|
9
|
+
if (passed) {
|
|
10
|
+
const dbPath = join(homedir(), '.irlxleet', 'db.json');
|
|
11
|
+
const db = getDb();
|
|
12
|
+
const today = new Date().toISOString().split('T')[0];
|
|
13
|
+
|
|
14
|
+
if (!db.streaks[today]) {
|
|
15
|
+
db.streaks[today] = { problems_solved: 0, total_time_ms: 0 };
|
|
16
|
+
}
|
|
17
|
+
db.streaks[today].problems_solved += 1;
|
|
18
|
+
db.streaks[today].total_time_ms += timeMs;
|
|
19
|
+
|
|
20
|
+
writeFileSync(dbPath, JSON.stringify(db, null, 2));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getCurrentStreak() {
|
|
25
|
+
const db = getDb();
|
|
26
|
+
const dates = Object.keys(db.streaks).sort().reverse();
|
|
27
|
+
if (dates.length === 0) return 0;
|
|
28
|
+
|
|
29
|
+
let streak = 0;
|
|
30
|
+
let current = new Date();
|
|
31
|
+
|
|
32
|
+
for (const dStr of dates) {
|
|
33
|
+
const d = new Date(dStr);
|
|
34
|
+
if (d.toDateString() === current.toDateString() || streak > 0) {
|
|
35
|
+
streak++;
|
|
36
|
+
current.setDate(current.getDate() - 1);
|
|
37
|
+
} else {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return streak;
|
|
42
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "two-sum",
|
|
4
|
+
"title": "Two Sum",
|
|
5
|
+
"category": "arrays",
|
|
6
|
+
"difficulty": "easy",
|
|
7
|
+
"companies": ["Google", "Amazon", "Meta"],
|
|
8
|
+
"description": "Given an array of integers `nums` and an integer `target`, return indices of the two numbers such that they add up to `target`.\n\nYou may assume that each input would have exactly one solution, and you may not use the same element twice.",
|
|
9
|
+
"examples": [
|
|
10
|
+
{ "input": "nums = [2,7,11,15], target = 9", "output": "[0,1]", "explanation": "Because nums[0] + nums[1] == 9" }
|
|
11
|
+
],
|
|
12
|
+
"constraints": ["2 <= nums.length <= 10^4", "-10^9 <= nums[i] <= 10^9"],
|
|
13
|
+
"testCases": {
|
|
14
|
+
"visible": [
|
|
15
|
+
{ "input": [[2,7,11,15], 9], "expected": [0,1] },
|
|
16
|
+
{ "input": [[3,2,4], 6], "expected": [1,2] }
|
|
17
|
+
],
|
|
18
|
+
"hidden": [
|
|
19
|
+
{ "input": [[3,3], 6], "expected": [0,1] },
|
|
20
|
+
{ "input": [[-1,-2,-3,-4,-5], -8], "expected": [2,4] }
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"functionName": "twoSum",
|
|
24
|
+
"parameterNames": ["nums", "target"],
|
|
25
|
+
"hints": ["Think about what complement means for each number", "Can you use a hash map to find complements in O(1)?"],
|
|
26
|
+
"optimalComplexity": { "time": "O(n)", "space": "O(n)" },
|
|
27
|
+
"tags": ["hash-map", "array"]
|
|
28
|
+
}
|
|
29
|
+
]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
const categoriesDir = join(__dirname, 'categories');
|
|
8
|
+
|
|
9
|
+
let problemsCache = null;
|
|
10
|
+
|
|
11
|
+
function loadProblems() {
|
|
12
|
+
if (problemsCache) return problemsCache;
|
|
13
|
+
|
|
14
|
+
problemsCache = [];
|
|
15
|
+
try {
|
|
16
|
+
const files = readdirSync(categoriesDir).filter(f => f.endsWith('.json'));
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
const content = readFileSync(join(categoriesDir, file), 'utf-8');
|
|
19
|
+
const problems = JSON.parse(content);
|
|
20
|
+
problemsCache.push(...problems);
|
|
21
|
+
}
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('Error loading problems:', error.message);
|
|
24
|
+
}
|
|
25
|
+
return problemsCache;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getAllProblems() {
|
|
29
|
+
return loadProblems();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getProblemsByCategory(category) {
|
|
33
|
+
return loadProblems().filter(p => p.category === category);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getProblemsByDifficulty(difficulty) {
|
|
37
|
+
return loadProblems().filter(p => p.difficulty === difficulty);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getProblemById(id) {
|
|
41
|
+
return loadProblems().find(p => p.id === id);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getRandomProblem(filters = {}) {
|
|
45
|
+
let list = loadProblems();
|
|
46
|
+
if (filters.category) list = list.filter(p => p.category === filters.category);
|
|
47
|
+
if (filters.difficulty) list = list.filter(p => p.difficulty === filters.difficulty);
|
|
48
|
+
if (filters.excludeSolved) {
|
|
49
|
+
// Assuming filters.excludeSolved is an array of solved IDs
|
|
50
|
+
list = list.filter(p => !filters.excludeSolved.includes(p.id));
|
|
51
|
+
}
|
|
52
|
+
if (list.length === 0) return null;
|
|
53
|
+
return list[Math.floor(Math.random() * list.length)];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getCategories() {
|
|
57
|
+
const problems = loadProblems();
|
|
58
|
+
const cats = new Set(problems.map(p => p.category));
|
|
59
|
+
return Array.from(cats);
|
|
60
|
+
}
|
package/src/store/db.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
const dir = join(homedir(), '.irlxleet');
|
|
6
|
+
const dbPath = join(dir, 'db.json');
|
|
7
|
+
|
|
8
|
+
function readDb() {
|
|
9
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
10
|
+
if (!existsSync(dbPath)) {
|
|
11
|
+
return { solutions: [], streaks: {}, mistakes: {} };
|
|
12
|
+
}
|
|
13
|
+
return JSON.parse(readFileSync(dbPath, 'utf-8'));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function writeDb(data) {
|
|
17
|
+
writeFileSync(dbPath, JSON.stringify(data, null, 2));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getDb() {
|
|
21
|
+
return readDb(); // Return current state
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function saveSolution(problemId, code, language, passed, totalTests, passedTests, attempts, timeMs) {
|
|
25
|
+
const db = readDb();
|
|
26
|
+
db.solutions.push({
|
|
27
|
+
problem_id: problemId,
|
|
28
|
+
code,
|
|
29
|
+
language,
|
|
30
|
+
passed: passed ? 1 : 0,
|
|
31
|
+
total_tests: totalTests,
|
|
32
|
+
passed_tests: passedTests,
|
|
33
|
+
attempts,
|
|
34
|
+
time_ms: timeMs,
|
|
35
|
+
created_at: new Date().toISOString()
|
|
36
|
+
});
|
|
37
|
+
writeDb(db);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getSolutionHistory(problemId) {
|
|
41
|
+
return readDb().solutions.filter(s => s.problem_id === problemId).reverse();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getMistakeCount(problemId) {
|
|
45
|
+
return readDb().mistakes[problemId] || 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function incrementMistake(problemId) {
|
|
49
|
+
const db = readDb();
|
|
50
|
+
db.mistakes[problemId] = (db.mistakes[problemId] || 0) + 1;
|
|
51
|
+
writeDb(db);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function resetMistake(problemId) {
|
|
55
|
+
const db = readDb();
|
|
56
|
+
db.mistakes[problemId] = 0;
|
|
57
|
+
writeDb(db);
|
|
58
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function runMigrations(db) {
|
|
2
|
+
db.exec(`
|
|
3
|
+
CREATE TABLE IF NOT EXISTS solutions (
|
|
4
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
5
|
+
problem_id TEXT NOT NULL,
|
|
6
|
+
code TEXT,
|
|
7
|
+
language TEXT,
|
|
8
|
+
passed INTEGER,
|
|
9
|
+
total_tests INTEGER,
|
|
10
|
+
passed_tests INTEGER,
|
|
11
|
+
attempts INTEGER,
|
|
12
|
+
time_ms INTEGER,
|
|
13
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE TABLE IF NOT EXISTS streaks (
|
|
17
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
18
|
+
date TEXT UNIQUE NOT NULL,
|
|
19
|
+
problems_solved INTEGER DEFAULT 0,
|
|
20
|
+
total_time_ms INTEGER DEFAULT 0
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS stats (
|
|
24
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
25
|
+
key TEXT UNIQUE NOT NULL,
|
|
26
|
+
value TEXT
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
CREATE TABLE IF NOT EXISTS mistake_tracker (
|
|
30
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
31
|
+
problem_id TEXT UNIQUE NOT NULL,
|
|
32
|
+
mistake_count INTEGER DEFAULT 0,
|
|
33
|
+
last_mistake_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
34
|
+
);
|
|
35
|
+
`);
|
|
36
|
+
}
|
package/src/ui/banner.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import figlet from 'figlet';
|
|
2
|
+
import gradient from 'gradient-string';
|
|
3
|
+
|
|
4
|
+
const taglines = [
|
|
5
|
+
"Code. Break. Become Unbreakable.",
|
|
6
|
+
"Your streak is the only thing keeping DOOM Coach away.",
|
|
7
|
+
"FAANG ready? We'll see about that.",
|
|
8
|
+
"Eat, Sleep, LeetCode, Repeat.",
|
|
9
|
+
"Runtime Error is just DOOM Coach laughing at you."
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export function showBanner() {
|
|
13
|
+
const text = figlet.textSync('irlxleet', { font: 'Slant' });
|
|
14
|
+
console.log(gradient.pastel.multiline(text));
|
|
15
|
+
const randomTagline = taglines[Math.floor(Math.random() * taglines.length)];
|
|
16
|
+
console.log(`\n 💀🔥 ${randomTagline} (v1.0.0)\n`);
|
|
17
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a visual progress bar string
|
|
6
|
+
* @param {number} current
|
|
7
|
+
* @param {number} total
|
|
8
|
+
* @param {number} [width=20]
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
export function createProgressBar(current, total, width = 20) {
|
|
12
|
+
const pct = total === 0 ? 0 : Math.round((current / total) * 100);
|
|
13
|
+
const filled = Math.round((pct / 100) * width);
|
|
14
|
+
const empty = width - filled;
|
|
15
|
+
const bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
16
|
+
|
|
17
|
+
const colorFn = pct > 70 ? chalk.green : pct > 40 ? chalk.yellow : chalk.red;
|
|
18
|
+
return `${colorFn(bar)} ${pct}%`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates an ora spinner (not started)
|
|
23
|
+
* @param {string} text
|
|
24
|
+
* @returns {import('ora').Ora}
|
|
25
|
+
*/
|
|
26
|
+
export function createSpinner(text) {
|
|
27
|
+
return ora({
|
|
28
|
+
text,
|
|
29
|
+
spinner: 'dots',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Animated countdown timer for timed challenges
|
|
35
|
+
* @param {number} seconds - total seconds to count down
|
|
36
|
+
* @returns {Promise<void>} resolves when countdown reaches 0
|
|
37
|
+
*/
|
|
38
|
+
export function showCountdown(seconds) {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
let remaining = seconds;
|
|
41
|
+
|
|
42
|
+
const formatTime = (secs) => {
|
|
43
|
+
const m = Math.floor(secs / 60).toString().padStart(2, '0');
|
|
44
|
+
const s = (secs % 60).toString().padStart(2, '0');
|
|
45
|
+
return `${m}:${s}`;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const render = () => {
|
|
49
|
+
const timeStr = formatTime(remaining);
|
|
50
|
+
let colorFn;
|
|
51
|
+
|
|
52
|
+
if (remaining <= 10) {
|
|
53
|
+
colorFn = chalk.bold.red;
|
|
54
|
+
} else if (remaining <= 30) {
|
|
55
|
+
colorFn = chalk.bold.yellow;
|
|
56
|
+
} else {
|
|
57
|
+
colorFn = chalk.bold.cyan;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
process.stdout.clearLine?.(0);
|
|
61
|
+
process.stdout.cursorTo?.(0);
|
|
62
|
+
process.stdout.write(colorFn(` ⏱️ ${timeStr} remaining`));
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
render();
|
|
66
|
+
|
|
67
|
+
const interval = setInterval(() => {
|
|
68
|
+
remaining--;
|
|
69
|
+
render();
|
|
70
|
+
|
|
71
|
+
if (remaining <= 0) {
|
|
72
|
+
clearInterval(interval);
|
|
73
|
+
process.stdout.write('\n');
|
|
74
|
+
console.log(chalk.bold.red(' 💀 TIME\'S UP!'));
|
|
75
|
+
resolve();
|
|
76
|
+
}
|
|
77
|
+
}, 1000);
|
|
78
|
+
});
|
|
79
|
+
}
|
package/src/ui/table.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Table from 'cli-table3';
|
|
2
|
+
import { theme } from './theme.js';
|
|
3
|
+
|
|
4
|
+
export function renderTestResults(results) {
|
|
5
|
+
const table = new Table({
|
|
6
|
+
head: ['Case', 'Input', 'Expected', 'Actual', 'Status', 'Time'],
|
|
7
|
+
style: { head: ['cyan'] }
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
results.forEach(res => {
|
|
11
|
+
let statusStr = res.passed ? theme.success('✅ PASS') : theme.error('❌ FAIL');
|
|
12
|
+
if (res.timedOut) statusStr = theme.warning('⏱️ TLE');
|
|
13
|
+
if (res.error) statusStr = theme.error('💥 ERR');
|
|
14
|
+
|
|
15
|
+
table.push([
|
|
16
|
+
res.caseNumber + (res.isHidden ? ' (hidden)' : ''),
|
|
17
|
+
JSON.stringify(res.input).substring(0, 30),
|
|
18
|
+
JSON.stringify(res.expected).substring(0, 30),
|
|
19
|
+
res.error ? 'Error' : JSON.stringify(res.actual).substring(0, 30),
|
|
20
|
+
statusStr,
|
|
21
|
+
`${res.time}ms`
|
|
22
|
+
]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
console.log(table.toString());
|
|
26
|
+
}
|
package/src/ui/theme.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
export const theme = {
|
|
4
|
+
doom: chalk.bold.red,
|
|
5
|
+
fire: chalk.bold.hex('#FF6B35'),
|
|
6
|
+
success: chalk.bold.green,
|
|
7
|
+
error: chalk.bold.red,
|
|
8
|
+
warning: chalk.bold.yellow,
|
|
9
|
+
info: chalk.bold.cyan,
|
|
10
|
+
muted: chalk.dim,
|
|
11
|
+
highlight: chalk.bold.magenta,
|
|
12
|
+
streak: chalk.bold.yellow,
|
|
13
|
+
rank: (name) => {
|
|
14
|
+
if (name === 'UNBREAKABLE') return chalk.bold.bgRed.white(` ${name} `);
|
|
15
|
+
return chalk.bold.cyan(name);
|
|
16
|
+
},
|
|
17
|
+
boxStyles: {
|
|
18
|
+
banner: { padding: 1, margin: 1, borderStyle: 'double', borderColor: 'red' },
|
|
19
|
+
hint: { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'cyan' },
|
|
20
|
+
explain: { padding: 1, margin: 1, borderStyle: 'classic', borderColor: 'yellow' },
|
|
21
|
+
gussa: { padding: 1, margin: 1, borderStyle: 'bold', borderColor: 'red' },
|
|
22
|
+
problem: { padding: 1, margin: 1, borderStyle: 'round', borderColor: '#FF6B35' }
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Problem: {{title}}
|
|
3
|
+
* Difficulty: {{difficulty}}
|
|
4
|
+
* Category: {{category}}
|
|
5
|
+
*
|
|
6
|
+
* {{description}}
|
|
7
|
+
*
|
|
8
|
+
* Constraints:
|
|
9
|
+
* {{constraints}}
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
function {{functionName}}({{params}}) {
|
|
13
|
+
// Your code here
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default {{functionName}};
|