Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/components/AlgoViz.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ export default function AlgoViz({ locale = 'en', initialAlgorithmId }: AlgoVizPr
{selectedAlgorithm ? (
<CodePanel
code={selectedAlgorithm.code}
codeSamples={selectedAlgorithm.codeSamples}
description={getLocalizedDescription(selectedAlgorithm)}
difficulty={selectedAlgorithm.difficulty}
currentLine={currentStepData?.codeLine}
Expand Down Expand Up @@ -493,6 +494,7 @@ export default function AlgoViz({ locale = 'en', initialAlgorithmId }: AlgoVizPr
{selectedAlgorithm ? (
<CodePanel
code={selectedAlgorithm.code}
codeSamples={selectedAlgorithm.codeSamples}
description={getLocalizedDescription(selectedAlgorithm)}
difficulty={selectedAlgorithm.difficulty}
currentLine={currentStepData?.codeLine}
Expand Down
137 changes: 133 additions & 4 deletions src/components/CodePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useState, useRef, useEffect, useCallback, useMemo, lazy, Suspense } fro
import type { Monaco } from '@monaco-editor/react'
import type { Locale } from '@i18n/translations'
import { translations } from '@i18n/translations'
import type { Difficulty } from '@lib/types'
import type { CodeLanguage, CodeSample, Difficulty } from '@lib/types'
import ComplexityChart from '@components/ComplexityChart'

const LazyEditor = lazy(() => import('@monaco-editor/react'))
Expand All @@ -29,8 +29,25 @@ const DIFFICULTY_CONFIG: Record<Difficulty, { en: string; es: string; color: str
},
}

const LANGUAGE_PRESETS: Record<CodeLanguage, { label: string; short: string; monaco: string }> = {
javascript: { label: 'JavaScript', short: 'JS', monaco: 'javascript' },
typescript: { label: 'TypeScript', short: 'TS', monaco: 'typescript' },
python: { label: 'Python', short: 'Py', monaco: 'python' },
cpp: { label: 'C++', short: 'C++', monaco: 'cpp' },
java: { label: 'Java', short: 'Java', monaco: 'java' },
}

interface EnrichedSample {
language: CodeLanguage
label: string
shortLabel: string
monacoLanguage: string
code: string
}

interface CodePanelProps {
code: string
codeSamples?: CodeSample[]
description: string
difficulty?: Difficulty
currentLine?: number
Expand Down Expand Up @@ -60,6 +77,7 @@ function defineTheme(monaco: Monaco) {

export default function CodePanel({
code,
codeSamples,
description,
difficulty,
currentLine,
Expand All @@ -75,6 +93,56 @@ export default function CodePanel({
const editorRef = useRef<any>(null)
const monacoRef = useRef<Monaco | null>(null)
const decorationsRef = useRef<string[]>([])
const samples = useMemo<EnrichedSample[]>(() => {
const seen = new Set<CodeLanguage>()
const enriched: EnrichedSample[] = []

const pushSample = (sample: { language: CodeLanguage; code: string; label?: string }) => {
if (seen.has(sample.language)) return
const preset = LANGUAGE_PRESETS[sample.language]
if (!preset) return
seen.add(sample.language)
enriched.push({
language: sample.language,
label: sample.label ?? preset.label,
shortLabel: preset.short,
monacoLanguage: preset.monaco,
code: sample.code,
})
}

pushSample({ language: 'javascript', code, label: LANGUAGE_PRESETS.javascript.label })
;(codeSamples ?? []).forEach((sample) => pushSample(sample))

return enriched
}, [code, codeSamples])
const [selectedLanguage, setSelectedLanguage] = useState<CodeLanguage>('javascript')

useEffect(() => {
if (samples.length === 0) return
if (!samples.some((s) => s.language === selectedLanguage)) {
setSelectedLanguage(samples[0].language)
}
}, [samples, selectedLanguage])

const activeSample = useMemo(() => {
if (samples.length === 0) {
const preset = LANGUAGE_PRESETS.javascript
return {
language: 'javascript' as CodeLanguage,
label: preset.label,
shortLabel: preset.short,
monacoLanguage: preset.monaco,
code,
}
}
return samples.find((s) => s.language === selectedLanguage) ?? samples[0]
}, [samples, selectedLanguage, code])

const editorLanguage = activeSample.monacoLanguage
const editorCode = activeSample.code
const hasMultipleLanguages = samples.length > 1
const showLanguageWarning = hasMultipleLanguages && activeSample.language !== 'javascript'

useEffect(() => {
setIsMounted(true)
Expand Down Expand Up @@ -223,15 +291,76 @@ export default function CodePanel({
id="tabpanel-code"
aria-labelledby="tab-code"
>
{hasMultipleLanguages && (
<div className="border-b border-white/8 px-4 py-2">
<div className="flex items-center justify-between gap-3 mb-2">
<span className="text-[10px] font-semibold uppercase tracking-[0.2em] text-neutral-500">
{t.codeLanguageLabel}
</span>
<span className="text-[11px] text-neutral-400 font-mono">
{activeSample.label}
</span>
</div>
<div
className="flex flex-wrap gap-1.5"
role="radiogroup"
aria-label={t.codeLanguageLabel}
>
{samples.map((sample) => {
const isActive = sample.language === selectedLanguage
return (
<button
key={sample.language}
type="button"
role="radio"
aria-checked={isActive}
tabIndex={isActive ? 0 : -1}
onClick={() => setSelectedLanguage(sample.language)}
className={`px-3 py-1.5 rounded-md border text-[11px] font-semibold tracking-wide transition-colors ${
isActive
? 'bg-white/10 border-white/30 text-white'
: 'border-white/10 text-neutral-500 hover:text-white/80 hover:border-white/20'
}`}
>
<span className="font-mono mr-1 text-[10px] uppercase tracking-[0.3em]">
{sample.shortLabel}
</span>
<span>{sample.label}</span>
</button>
)
})}
</div>
{showLanguageWarning && (
<div className="mt-3 flex items-start gap-2 text-[11px] text-amber-300/80">
<svg
className="w-3.5 h-3.5 shrink-0 mt-0.5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.5}
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 9v4m0 4h.01M10.29 3.86L2.82 17.25A1.5 1.5 0 004.12 19.5h15.76a1.5 1.5 0 001.3-2.25L13.71 3.86a1.5 1.5 0 00-2.42 0z"
/>
</svg>
<span className="leading-tight">{t.codeLanguageWarning}</span>
</div>
)}
</div>
)}
<div
className="flex-1 overflow-hidden transition-opacity duration-500 ease-in-out"
style={{ opacity: editorReady ? 1 : 0 }}
>
{isMounted && (
<Suspense fallback={null}>
<LazyEditor
defaultLanguage="javascript"
value={code}
language={editorLanguage}
value={editorCode}
path={`algorithm-${activeSample.language}`}
theme="vs-dark"
onMount={handleEditorDidMount}
loading={null}
Expand Down Expand Up @@ -274,7 +403,7 @@ export default function CodePanel({
)}
</div>

{/* Console output panel */}
{/* Console output panel asd */}
{consoleOutput && consoleOutput.length > 0 && (
<div
className="shrink-0 border-t border-white/[0.08] max-h-[140px] flex flex-col"
Expand Down
6 changes: 6 additions & 0 deletions src/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export interface Translations {
selectAlgorithmCode: string
expandCodePanel: string
collapseCodePanel: string
codeLanguageLabel: string
codeLanguageWarning: string
variables: string

// Controls
Expand Down Expand Up @@ -86,6 +88,8 @@ export const translations: Record<Locale, Translations> = {
selectAlgorithmCode: 'Select an algorithm to view its code',
expandCodePanel: 'Expand code panel',
collapseCodePanel: 'Collapse code panel',
codeLanguageLabel: 'Language',
codeLanguageWarning: 'Step-by-step highlighting is only synced with JavaScript right now.',
variables: 'Variables',

speed: 'Speed',
Expand Down Expand Up @@ -996,6 +1000,8 @@ The puzzle was invented by mathematician Édouard Lucas in 1883. Legend says mon
selectAlgorithmCode: 'Selecciona un algoritmo para ver su código',
expandCodePanel: 'Expandir panel de código',
collapseCodePanel: 'Contraer panel de código',
codeLanguageLabel: 'Lenguaje',
codeLanguageWarning: 'El paso a paso solo se sincroniza con JavaScript por ahora.',
variables: 'Variables',

speed: 'Velocidad',
Expand Down
66 changes: 66 additions & 0 deletions src/lib/algorithms/backtracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,72 @@ const nQueens: Algorithm = {
solve(0);
return board;
}`,
codeSamples: [
{
language: 'cpp',
code: `#include <vector>
#include <string>

bool solve(int row, int n, std::vector<std::string>& board,
std::vector<bool>& cols,
std::vector<bool>& diag1,
std::vector<bool>& diag2) {
if (row == n) return true;
for (int col = 0; col < n; ++col) {
if (cols[col] || diag1[row + col] || diag2[row - col + n - 1]) continue;
board[row][col] = 'Q';
cols[col] = diag1[row + col] = diag2[row - col + n - 1] = true;
if (solve(row + 1, n, board, cols, diag1, diag2)) return true;
cols[col] = diag1[row + col] = diag2[row - col + n - 1] = false;
board[row][col] = '.';
}
return false;
}

std::vector<std::string> solveNQueens(int n) {
std::vector<std::string> board(n, std::string(n, '.'));
std::vector<bool> cols(n, false);
std::vector<bool> diag1(2 * n - 1, false);
std::vector<bool> diag2(2 * n - 1, false);
solve(0, n, board, cols, diag1, diag2);
return board;
}`,
},
{
language: 'java',
code: `import java.util.Arrays;

class NQueensSolver {
static char[][] solveNQueens(int n) {
char[][] board = new char[n][n];
for (char[] row : board) {
Arrays.fill(row, '.');
}
boolean[] cols = new boolean[n];
boolean[] diag1 = new boolean[2 * n - 1];
boolean[] diag2 = new boolean[2 * n - 1];
backtrack(0, n, board, cols, diag1, diag2);
return board;
}

private static boolean backtrack(int row, int n, char[][] board,
boolean[] cols,
boolean[] diag1,
boolean[] diag2) {
if (row == n) return true;
for (int col = 0; col < n; col++) {
if (cols[col] || diag1[row + col] || diag2[row - col + n - 1]) continue;
board[row][col] = 'Q';
cols[col] = diag1[row + col] = diag2[row - col + n - 1] = true;
if (backtrack(row + 1, n, board, cols, diag1, diag2)) return true;
board[row][col] = '.';
cols[col] = diag1[row + col] = diag2[row - col + n - 1] = false;
}
return false;
}
}`,
},
],
description: `N-Queens Problem

The N-Queens problem asks: how can N chess queens be placed on an N×N chessboard so that no two queens threaten each other?
Expand Down
52 changes: 52 additions & 0 deletions src/lib/algorithms/concepts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,58 @@ export const slidingWindow: Algorithm = {
}
return s.slice(bestStart, bestStart + best);
}`,
codeSamples: [
{
language: 'cpp',
code: `#include <string>
#include <unordered_set>

std::string longestUniqueSubstring(const std::string& s) {
std::unordered_set<char> window;
std::size_t start = 0, bestStart = 0, bestLen = 0;

for (std::size_t end = 0; end < s.size(); ++end) {
while (window.count(s[end])) {
window.erase(s[start]);
++start;
}
window.insert(s[end]);
if (end - start + 1 > bestLen) {
bestLen = end - start + 1;
bestStart = start;
}
}

return s.substr(bestStart, bestLen);
}`,
},
{
language: 'java',
code: `import java.util.HashSet;
import java.util.Set;

class SlidingWindow {
static String longestUniqueSubstring(String s) {
Set<Character> window = new HashSet<>();
int start = 0, bestStart = 0, bestLen = 0;

for (int end = 0; end < s.length(); end++) {
while (window.contains(s.charAt(end))) {
window.remove(s.charAt(start));
start++;
}
window.add(s.charAt(end));
if (end - start + 1 > bestLen) {
bestLen = end - start + 1;
bestStart = start;
}
}

return s.substring(bestStart, bestStart + bestLen);
}
}`,
},
],
description: `Sliding Window

Sliding Window maintains a dynamic range (window) over a sequence, expanding and contracting to solve substring/subarray problems efficiently.
Expand Down
Loading