Skip to content

Commit bcd1a80

Browse files
committed
feat: Add Java quiz generation and console GUI
- Implemented `generate_java_questions.py` to convert Java questions from README to JSON format. - Created `java_quiz_console.py` for a console-based quiz application with random question selection and results display. - Developed `java_quiz_win.py` for a GUI-based quiz application using Tkinter, featuring question navigation and feedback. - Updated `pom.xml` to specify Java version for compilation. - Refined `readme.md` to improve structure and remove outdated coding challenges.
1 parent 43d4fe6 commit bcd1a80

9 files changed

Lines changed: 4181 additions & 482 deletions
6.01 KB
Binary file not shown.

code-samples.md

Lines changed: 453 additions & 0 deletions
Large diffs are not rendered by default.

config.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Configuration for Java Quiz apps
2+
# You can customize fonts, window, thresholds, and limits here.
3+
4+
# Multiplier applied to all base font sizes below
5+
font_scale: 2.0 # doubles the font size by default
6+
7+
# Base font family and sizes (before scale)
8+
fonts:
9+
family: "Segoe UI"
10+
title: 16 # top title label
11+
question: 12 # question text
12+
option: 12 # options (radio buttons)
13+
explanation: 10 # explanation label/text
14+
feedback: 10 # bottom feedback label
15+
16+
# Window settings for the Windows GUI app
17+
window:
18+
title: "Java Quiz (Windows)"
19+
width: 900
20+
height: 600
21+
min_width: 720
22+
min_height: 520
23+
24+
# Quiz logic
25+
max_questions: 60 # if more exist, pick a random subset of this many
26+
pass_threshold: 0.8 # 80% required to pass
27+
28+

data/java-questions.json

Lines changed: 2927 additions & 0 deletions
Large diffs are not rendered by default.

generate_java_questions.py

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Programa para convertir preguntas de Java del archivo readme.md
4+
al formato JSON de opción múltiple para react-certification-course
5+
"""
6+
7+
import json
8+
import re
9+
import random
10+
import os
11+
12+
def read_java_readme():
13+
"""Lee el archivo readme.md con las preguntas de Java"""
14+
readme_path = os.environ.get('README_PATH', os.path.join(os.getcwd(), 'readme.md'))
15+
try:
16+
with open(readme_path, 'r', encoding='utf-8') as file:
17+
content = file.read()
18+
return content
19+
except FileNotFoundError:
20+
print(f"Error: No se encontró el archivo {readme_path}")
21+
return None
22+
23+
def extract_questions_from_readme(content):
24+
"""Extrae todas las preguntas del contenido del readme"""
25+
questions = []
26+
27+
# Patrón para encontrar preguntas con formato: **n . Pregunta**
28+
question_pattern = r'-\s*\*\*(\d+)\s*\.\s*([^*]+?)\*\*'
29+
30+
# Buscar todas las preguntas
31+
question_matches = re.finditer(question_pattern, content, re.DOTALL)
32+
33+
current_category = "Java Basics"
34+
35+
for match in question_matches:
36+
question_num = int(match.group(1))
37+
question_text = match.group(2).strip()
38+
39+
# Determinar categoría basada en el número de pregunta y contenido
40+
category = determine_category(question_num, question_text, content)
41+
42+
# Extraer la respuesta de la pregunta
43+
answer_content = extract_answer_content(content, match.end())
44+
45+
if answer_content:
46+
question_data = {
47+
"id": question_num,
48+
"category": category,
49+
"question": question_text,
50+
"answer_content": answer_content
51+
}
52+
questions.append(question_data)
53+
54+
return questions
55+
56+
def determine_category(question_num, question_text, content):
57+
"""Determina la categoría de la pregunta basada en el número y contexto"""
58+
categories_map = {
59+
(1, 6): "Java Platform",
60+
(7, 15): "Wrapper Classes",
61+
(16, 25): "Strings",
62+
(26, 35): "Object-Oriented Programming",
63+
(36, 45): "Inheritance",
64+
(46, 55): "Collections",
65+
(56, 65): "Exception Handling",
66+
(66, 75): "Multithreading",
67+
(76, 85): "Generics",
68+
(86, 95): "Java 8 Features",
69+
(96, 105): "Spring Framework",
70+
(106, 115): "Database Connectivity",
71+
(116, 125): "Web Services",
72+
(126, 135): "Design Patterns",
73+
(136, 145): "JVM and Memory Management",
74+
(146, 155): "Testing",
75+
(156, 165): "Best Practices",
76+
(166, 175): "Performance Optimization",
77+
(176, 185): "Security",
78+
(186, 195): "Frameworks",
79+
(196, 205): "Advanced Topics",
80+
(206, 215): "Enterprise Development",
81+
(216, 230): "Modern Java Features"
82+
}
83+
84+
# Buscar la categoría apropiada
85+
for (start, end), category in categories_map.items():
86+
if start <= question_num <= end:
87+
return category
88+
89+
# Categoría por defecto o basada en contenido
90+
question_lower = question_text.lower()
91+
if any(word in question_lower for word in ['string', 'immutable', 'stringbuilder']):
92+
return "Strings"
93+
elif any(word in question_lower for word in ['collection', 'list', 'set', 'map']):
94+
return "Collections"
95+
elif any(word in question_lower for word in ['thread', 'synchronized', 'concurrent']):
96+
return "Multithreading"
97+
elif any(word in question_lower for word in ['exception', 'try', 'catch', 'finally']):
98+
return "Exception Handling"
99+
elif any(word in question_lower for word in ['jvm', 'memory', 'garbage']):
100+
return "JVM and Memory Management"
101+
else:
102+
return "Java Basics"
103+
104+
def extract_answer_content(content, start_pos):
105+
"""Extrae el contenido de la respuesta después de una pregunta"""
106+
# Buscar desde la posición actual hasta la siguiente pregunta
107+
next_question_pattern = r'-\s*\*\*\d+\s*\.\s*[^*]+?\*\*'
108+
next_match = re.search(next_question_pattern, content[start_pos:])
109+
110+
if next_match:
111+
end_pos = start_pos + next_match.start()
112+
answer_section = content[start_pos:end_pos]
113+
else:
114+
# Si no hay más preguntas, tomar hasta el final
115+
answer_section = content[start_pos:]
116+
117+
return answer_section.strip()
118+
119+
def create_multiple_choice_question(question_data):
120+
"""Convierte una pregunta en formato de opción múltiple"""
121+
question_text = question_data["question"]
122+
answer_content = question_data["answer_content"]
123+
124+
# Extraer la respuesta correcta del contenido
125+
correct_answer = extract_correct_answer(answer_content)
126+
127+
# Generar opciones incorrectas (distractores)
128+
distractors = generate_distractors(question_text, correct_answer)
129+
130+
# Mezclar opciones
131+
all_options = [correct_answer] + distractors
132+
random.shuffle(all_options)
133+
134+
# Encontrar la posición de la respuesta correcta
135+
correct_index = all_options.index(correct_answer)
136+
answer_letter = chr(ord('a') + correct_index)
137+
138+
# Generar explicación
139+
explanation = generate_explanation(answer_content)
140+
141+
return {
142+
"id": question_data["id"],
143+
"category": question_data["category"],
144+
"question": question_text,
145+
"options": all_options,
146+
"answer": answer_letter,
147+
"explanation": explanation
148+
}
149+
150+
def extract_correct_answer(answer_content):
151+
"""Extrae la respuesta correcta del contenido de la respuesta"""
152+
# Buscar puntos clave en la respuesta
153+
lines = answer_content.split('\n')
154+
155+
# Tomar las primeras líneas más relevantes
156+
relevant_lines = []
157+
for line in lines[:10]: # Primeras 10 líneas
158+
line = line.strip()
159+
if line and not line.startswith('-') and not line.startswith('*'):
160+
if len(line) > 20 and len(line) < 200: # Longitud apropiada para opción
161+
relevant_lines.append(line)
162+
163+
if relevant_lines:
164+
return relevant_lines[0]
165+
else:
166+
# Respuesta genérica si no se puede extraer
167+
return "Esta es la respuesta correcta basada en el contenido de Java."
168+
169+
def generate_distractors(question_text, correct_answer):
170+
"""Genera opciones incorrectas plausibles"""
171+
question_lower = question_text.lower()
172+
173+
# Distractores generales para diferentes tipos de preguntas
174+
general_distractors = [
175+
"Esta opción es incorrecta para esta pregunta de Java.",
176+
"Esta no es la respuesta correcta según los estándares de Java.",
177+
"Esta opción no aplica a este concepto de programación Java."
178+
]
179+
180+
# Distractores específicos por tema
181+
if any(word in question_lower for word in ['jvm', 'platform', 'bytecode']):
182+
specific_distractors = [
183+
"Java se compila directamente a código máquina específico de la plataforma.",
184+
"Java requiere recompilación para cada sistema operativo diferente.",
185+
"Java no puede ejecutarse en diferentes sistemas operativos."
186+
]
187+
elif any(word in question_lower for word in ['string', 'immutable']):
188+
specific_distractors = [
189+
"Los objetos String en Java son completamente mutables.",
190+
"StringBuffer y StringBuilder son inmutables en Java.",
191+
"Java no tiene soporte para cadenas de texto inmutables."
192+
]
193+
elif any(word in question_lower for word in ['wrapper', 'boxing']):
194+
specific_distractors = [
195+
"Java no soporta conversión automática entre primitivos y objetos.",
196+
"Los tipos wrapper consumen menos memoria que los primitivos.",
197+
"Autoboxing solo funciona con tipos numéricos, no con boolean o char."
198+
]
199+
elif any(word in question_lower for word in ['collection', 'list', 'set']):
200+
specific_distractors = [
201+
"Las colecciones en Java solo pueden almacenar tipos primitivos.",
202+
"ArrayList y LinkedList tienen el mismo rendimiento en todas las operaciones.",
203+
"Set permite elementos duplicados en Java."
204+
]
205+
elif any(word in question_lower for word in ['thread', 'synchronized']):
206+
specific_distractors = [
207+
"Java no soporta programación concurrente nativa.",
208+
"Synchronized solo funciona con métodos estáticos.",
209+
"Los threads en Java no pueden compartir memoria."
210+
]
211+
else:
212+
specific_distractors = [
213+
"Esta funcionalidad no existe en Java.",
214+
"Java no soporta esta característica hasta versiones muy recientes.",
215+
"Esta es una característica exclusiva de otros lenguajes de programación."
216+
]
217+
218+
# Combinar distractores y seleccionar 3
219+
all_distractors = specific_distractors + general_distractors
220+
221+
# Asegurar que no repetimos la respuesta correcta
222+
unique_distractors = [d for d in all_distractors if d != correct_answer]
223+
224+
# Seleccionar 3 distractores únicos
225+
selected_distractors = []
226+
for distractor in unique_distractors:
227+
if len(selected_distractors) < 3:
228+
selected_distractors.append(distractor)
229+
230+
# Si necesitamos más distractores, generar algunos genéricos
231+
while len(selected_distractors) < 3:
232+
generic = f"Opción incorrecta número {len(selected_distractors) + 1} para esta pregunta."
233+
selected_distractors.append(generic)
234+
235+
return selected_distractors[:3]
236+
237+
def generate_explanation(answer_content):
238+
"""Genera una explicación basada en el contenido de la respuesta"""
239+
# Tomar las primeras líneas del contenido como explicación
240+
lines = answer_content.split('\n')
241+
explanation_parts = []
242+
243+
for line in lines[:5]: # Primeras 5 líneas
244+
line = line.strip()
245+
if line and not line.startswith('-') and not line.startswith('*'):
246+
if len(line) > 10:
247+
explanation_parts.append(line)
248+
249+
if explanation_parts:
250+
explanation = ' '.join(explanation_parts[:2]) # Tomar las dos primeras partes
251+
# Limpiar la explicación
252+
explanation = re.sub(r'\*\*([^*]+)\*\*', r'\1', explanation) # Remover markdown bold
253+
explanation = re.sub(r'```[^`]*```', '', explanation) # Remover bloques de código
254+
explanation = explanation.replace(' ', ' ').strip()
255+
return explanation if len(explanation) < 500 else explanation[:500] + "..."
256+
257+
return "Esta es la respuesta correcta basada en las mejores prácticas y estándares de Java."
258+
259+
def main():
260+
"""Función principal"""
261+
print("Iniciando conversión de preguntas de Java...")
262+
263+
# Leer el archivo readme
264+
content = read_java_readme()
265+
if not content:
266+
return
267+
268+
print("Archivo leído correctamente. Extrayendo preguntas...")
269+
270+
# Extraer preguntas
271+
questions_data = extract_questions_from_readme(content)
272+
print(f"Encontradas {len(questions_data)} preguntas")
273+
274+
# Convertir a formato de opción múltiple
275+
java_questions = []
276+
for i, question_data in enumerate(questions_data):
277+
print(f"Procesando pregunta {i+1}/{len(questions_data)}: {question_data['question'][:50]}...")
278+
try:
279+
mc_question = create_multiple_choice_question(question_data)
280+
java_questions.append(mc_question)
281+
except Exception as e:
282+
print(f"Error procesando pregunta {question_data['id']}: {e}")
283+
continue
284+
285+
print(f"Procesadas {len(java_questions)} preguntas exitosamente")
286+
287+
# Guardar en archivo JSON
288+
script_dir = os.path.dirname(os.path.abspath(__file__))
289+
output_dir = os.path.join(script_dir, "./data")
290+
output_path = os.path.join(output_dir, "java-questions.json")
291+
292+
# Ensure output directory exists
293+
os.makedirs(output_dir, exist_ok=True)
294+
print(f"Guardando preguntas en {output_path}...")
295+
296+
try:
297+
with open(output_path, 'w', encoding='utf-8') as f:
298+
json.dump(java_questions, f, ensure_ascii=False, indent=2)
299+
300+
print(f"Archivo guardado exitosamente en: {output_path}")
301+
print(f"Total de preguntas generadas: {len(java_questions)}")
302+
303+
# Mostrar algunas estadísticas
304+
categories = {}
305+
for q in java_questions:
306+
cat = q['category']
307+
categories[cat] = categories.get(cat, 0) + 1
308+
309+
print("\nDistribución por categorías:")
310+
for cat, count in sorted(categories.items()):
311+
print(f" {cat}: {count} preguntas")
312+
313+
except Exception as e:
314+
print(f"Error guardando el archivo: {e}")
315+
316+
if __name__ == "__main__":
317+
main()

0 commit comments

Comments
 (0)