🏗️ Architecture Technique - KodLab
Cette documentation détaille l'architecture interne de KodLab, le processus de compilation, et le fonctionnement de l'émulateur.
📋 Table des Matières
- Vue d'Ensemble
- Compilateur Kod
- Émulateur de Console
- Interface Utilisateur
- Flux de Données
- Gestion des Erreurs
- Performance et Optimisations
Vue d'Ensemble
Architecture Modulaire
┌─────────────────────────────────────────────────────┐
│ index.html │
│ Interface Utilisateur + Monaco Editor + Canvas │
└─────────────────┬───────────────────────────────────┘
│
┌─────────────────┴───────────────────────────────────┐
│ main.js │
│ Orchestration et Communication │
└─────────┬─────────────────────────┬─────────────────┘
│ │
┌─────────┴─────────┐ ┌─────────┴─────────────────┐
│ kodCompiler.js │ │ kodEmulator.js │
│ .kod → JavaScript│ │ Exécution + Canvas │
└───────────────────┘ └───────────────────────────┘
Technologies Utilisées
- Frontend : HTML5, CSS3, JavaScript ES6+
- Éditeur : Monaco Editor (VS Code)
- Rendu : Canvas 2D Context
- Parsing : Expressions régulières et AST simple
- Compilation : Transpilation vers JavaScript ES6 Classes
Compilateur Kod
Processus de Compilation
1. Analyse Lexicale (Tokenisation)
// kodCompiler.js - Méthode compile()
compile(kodCode) {
// 1. Nettoyage et préparation
const lines = kodCode
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0 && !line.startsWith('//'));
// 2. Séparation variables/fonctions
let variables = [];
let functions = [];
let i = 0;
// 3. Collecte des variables globales
while (i < lines.length && !lines[i].startsWith('function')) {
const varMatch = line.match(/^let\s+(\w+)\s*=\s*(.+)$/);
if (varMatch) {
variables.push({ name: varMatch[1], value: varMatch[2] });
}
i++;
}
// 4. Collecte et compilation des fonctions
// ...
}
2. Analyse Syntaxique (Parsing)
// Extraction des fonctions avec gestion des blocs
while (i < lines.length) {
if (lines[i].startsWith('function') || lines[i].match(/^(start|update)\s*\(/)) {
const funcName = this.extractFunctionName(lines[i]);
i++; // Passe la ligne de déclaration
let funcBody = [];
let braceLevel = 0;
// Parse du corps de fonction
while (i < lines.length && (lines[i] !== 'end' || braceLevel > 0)) {
const line = lines[i];
if (line === 'end') {
braceLevel--;
funcBody.push(' }');
} else {
const compiled = this.compileStatement(line);
if (compiled.trim()) {
funcBody.push(' ' + compiled);
// Détection des structures de contrôle
if (compiled.includes('if (') ||
compiled.includes('for (') ||
compiled.includes('while (')) {
braceLevel++;
}
}
}
i++;
}
functions.push({ name: funcName, body: funcBody });
}
i++;
}
3. Compilation des Instructions
compileStatement(line) {
// 1. Structures de contrôle
if (line.startsWith('if ')) {
return this.compileIf(line);
} else if (line.startsWith('else if ')) {
return this.compileElseIf(line);
} else if (line === 'else') {
return '} else {';
} else if (line.startsWith('for ')) {
return this.compileFor(line);
} else if (line.startsWith('while ')) {
return this.compileWhile(line);
}
// 2. Assignations
else if (line.match(/^\w+\s*=\s*.+$/)) {
return this.compileAssignment(line);
}
// 3. Appels de fonction
else {
return this.compileFunctionCall(line);
}
}
// Compilation spécifique des conditions
compileIf(line) {
const condition = line.substring(3).trim(); // Enlève 'if '
const jsCondition = this.compileExpression(condition);
return `if (${jsCondition}) {`;
}
// Compilation des expressions
compileExpression(expr) {
// Opérateurs logiques Kod → JavaScript
expr = expr.replace(/\s+and\s+/g, ' && ');
expr = expr.replace(/\s+or\s+/g, ' || ');
expr = expr.replace(/\s+not\s+/g, ' !');
// Appels de fonctions API
expr = expr.replace(/isKeyPressed\s*\(/g, 'this.isKeyPressed(');
// Variables → propriétés de classe
expr = expr.replace(/\b(\w+)\b/g, (match) => {
if (this.isVariable(match)) {
return `this.${match}`;
}
return match;
});
return expr;
}
4. Génération du Code JavaScript
generateGameClass(variables, functions) {
let code = `
// Code généré automatiquement par le compilateur .kod
class KodGame {
constructor(emulator) {
this.emulator = emulator;
this.ctx = emulator.ctx;
this.canvas = emulator.canvas;
this.keys = emulator.keys;
// Variables globales du jeu
`;
// Injection des variables comme propriétés
for (let variable of variables) {
code += ` this.${variable.name} = ${variable.value};\n`;
}
code += ` }
// === API GRAPHIQUE ===
clearScreen(color = '#000000') {
this.ctx.fillStyle = color;
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
drawRect(x, y, width, height, color) {
this.ctx.fillStyle = color;
this.ctx.fillRect(x, y, width, height);
}
// ... autres méthodes API ...
// === FONCTIONS UTILISATEUR ===
`;
// Injection des fonctions compilées
for (let func of functions) {
code += ` ${func.name}() {\n`;
code += func.body.join('\n') + '\n';
code += ` }\n\n`;
}
code += `}`;
// Nettoyage des références doubles
code = code.replace(/this\.this\./g, 'this.');
code = code.replace(/\.{2,}/g, '.');
return code;
}
Gestion des Erreurs de Compilation
compile(kodCode) {
try {
// Processus de compilation...
return { success: true, code: jsCode };
} catch (error) {
return {
success: false,
error: this.formatCompilerError(error)
};
}
}
formatCompilerError(error) {
// Localisation de l'erreur
const lineMatch = error.message.match(/line (\d+)/);
if (lineMatch) {
const lineNum = parseInt(lineMatch[1]);
return `Erreur ligne ${lineNum}: ${error.message}`;
}
// Erreurs de syntaxe courantes
if (error.message.includes('unexpected token')) {
return 'Erreur de syntaxe: vérifiez les parenthèses et la structure des blocs';
}
return `Erreur de compilation: ${error.message}`;
}
Émulateur de Console
Architecture de l'Émulateur
class KodEmulator {
constructor(canvasId) {
// Initialisation du canvas
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
// État de l'émulateur
this.gameInstance = null;
this.isRunning = false;
this.animationId = null;
// Métriques de performance
this.lastFrameTime = 0;
this.fps = 0;
this.frameCount = 0;
// Gestion des entrées
this.keys = {};
this.setupEventListeners();
this.setupCanvas();
}
}
Boucle de Jeu Principal
// Démarrage du jeu
run() {
if (!this.gameInstance) {
return { success: false, error: 'Aucun jeu chargé' };
}
this.isRunning = true;
this.lastFrameTime = performance.now();
// Appel initial de start()
if (typeof this.gameInstance.start === 'function') {
this.gameInstance.start();
}
// Lancement de la boucle principale
this.gameLoop();
return { success: true, message: 'Jeu démarré' };
}
// Boucle de rendu 60 FPS
gameLoop() {
if (!this.isRunning) return;
const currentTime = performance.now();
const deltaTime = currentTime - this.lastFrameTime;
// Calcul FPS
this.frameCount++;
if (currentTime - this.fpsUpdateTime >= 1000) {
this.fps = Math.round(this.frameCount * 1000 / (currentTime - this.fpsUpdateTime));
this.frameCount = 0;
this.fpsUpdateTime = currentTime;
}
// Appel de la fonction update() du jeu
try {
if (typeof this.gameInstance.update === 'function') {
this.gameInstance.update();
}
} catch (error) {
this.handleRuntimeError(error);
return;
}
this.lastFrameTime = currentTime;
// Programmer la prochaine frame
this.animationId = requestAnimationFrame(() => this.gameLoop());
}
Gestion des Entrées
setupEventListeners() {
// Capture des événements clavier
document.addEventListener('keydown', (e) => {
this.handleKeyEvent(e, true);
});
document.addEventListener('keyup', (e) => {
this.handleKeyEvent(e, false);
});
// Prévention du scroll avec les flèches
document.addEventListener('keydown', (e) => {
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Space'].includes(e.code)) {
e.preventDefault();
}
});
}
handleKeyEvent(event, isPressed) {
// Mapping des touches
const keyMap = {
'ArrowUp': 'up',
'ArrowDown': 'down',
'ArrowLeft': 'left',
'ArrowRight': 'right',
'Space': 'space',
// Support WASD
'KeyW': 'up',
'KeyS': 'down',
'KeyA': 'left',
'KeyD': 'right'
};
const mappedKey = keyMap[event.code];
if (mappedKey) {
this.keys[mappedKey] = isPressed;
}
}
// API accessible depuis les jeux
isKeyPressed(key) {
return Boolean(this.keys[key]);
}
Configuration du Canvas
setupCanvas() {
// Configuration pour le pixel art
this.ctx.imageSmoothingEnabled = false;
this.canvas.style.imageRendering = 'pixelated';
// Écran noir par défaut
this.clearScreen('#000000');
// Message d'accueil
this.ctx.fillStyle = '#666';
this.ctx.font = '20px Arial';
this.ctx.textAlign = 'center';
this.ctx.fillText('KodLab Ready', this.canvas.width / 2, this.canvas.height / 2);
this.ctx.fillText('Compilez et lancez votre code!', this.canvas.width / 2, this.canvas.height / 2 + 30);
}
// API de rendu optimisée
clearScreen(color = '#000000') {
this.ctx.fillStyle = color;
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
drawRect(x, y, width, height, color) {
this.ctx.fillStyle = color;
this.ctx.fillRect(x, y, width, height);
}
drawCircle(x, y, radius, color) {
this.ctx.fillStyle = color;
this.ctx.beginPath();
this.ctx.arc(x, y, radius, 0, 2 * Math.PI);
this.ctx.fill();
}
drawText(text, x, y, color) {
this.ctx.fillStyle = color;
this.ctx.font = '16px Arial';
this.ctx.textAlign = 'left';
this.ctx.fillText(text, x, y);
}
Interface Utilisateur
Structure HTML
<!DOCTYPE html>
<html>
<head>
<title>KodLab</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<!-- En-tête -->
<header class="header">
<h1>🎮 KodLab</h1>
<div class="controls">
<select id="exampleSelect">
<option value="">Sélectionner un exemple</option>
</select>
</div>
</header>
<!-- Zone principale -->
<main class="main">
<!-- Éditeur de code -->
<section class="editor-section">
<div id="editor"></div>
<div class="editor-controls">
<button id="compileBtn">🔧 Compiler</button>
<button id="runBtn">▶️ Lancer</button>
<button id="stopBtn">⏹️ Stop</button>
</div>
</section>
<!-- Console de jeu -->
<section class="console-section">
<div class="console-header">
<h3>🎯 Console de Jeu</h3>
<span id="fpsDisplay">FPS: --</span>
</div>
<canvas id="gameCanvas" width="400" height="300"></canvas>
</section>
</main>
<!-- Console de debug -->
<footer class="console">
<div id="consoleOutput"></div>
</footer>
</div>
<!-- Scripts -->
<script src="https://unpkg.com/monaco-editor@0.44.0/min/vs/loader.js"></script>
<script src="kodCompiler.js"></script>
<script src="kodEmulator.js"></script>
<script src="main.js"></script>
</body>
</html>
Intégration Monaco Editor
// main.js - Configuration de l'éditeur
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.44.0/min/vs' }});
require(['vs/editor/editor.main'], function () {
// Configuration de la coloration syntaxique pour .kod
monaco.languages.register({ id: 'kod' });
monaco.languages.setMonarchTokensProvider('kod', {
tokenizer: {
root: [
// Mots-clés
[/\b(function|if|else|end|for|while|let|to|step)\b/, 'keyword'],
// Fonctions API
[/\b(clearScreen|drawRect|drawCircle|drawText|drawLine|isKeyPressed)\b/, 'function'],
// Nombres
[/\d+(\.\d+)?/, 'number'],
// Chaînes
[/"[^"]*"/, 'string'],
[/'[^']*'/, 'string'],
// Commentaires
[/\/\/.*$/, 'comment'],
// Opérateurs
[/[+\-*\/=<>!&|]/, 'operator'],
]
}
});
// Création de l'éditeur
window.editor = monaco.editor.create(document.getElementById('editor'), {
value: getDefaultCode(),
language: 'kod',
theme: 'vs-dark',
automaticLayout: true,
minimap: { enabled: false },
scrollBeyondLastLine: false,
fontSize: 14,
tabSize: 4
});
});
function getDefaultCode() {
return `// Bienvenue dans KodLab !
// Créez votre premier jeu en modifiant ce code
let x = 200
let y = 150
function start()
clearScreen("#000033")
end
function update()
clearScreen("#000033")
// Contrôles
if isKeyPressed("left")
x = x - 3
end
if isKeyPressed("right")
x = x + 3
end
// Affichage
drawCircle(x, y, 20, "#FF6600")
drawText("Utilisez les flèches!", 10, 20, "#FFFFFF")
end`;
}
Flux de Données
Cycle de Développement
1. Écriture du Code (.kod)
↓
2. Compilation (kodCompiler.js)
├─ Succès → Code JavaScript ES6
└─ Erreur → Message d'erreur
↓
3. Chargement dans l'Émulateur
├─ eval() du code compilé
├─ Création instance KodGame
└─ Liaison avec l'émulateur
↓
4. Exécution
├─ Appel start() (une fois)
├─ Boucle update() (60 FPS)
├─ Gestion des entrées
└─ Rendu Canvas
Communication entre Modules
// main.js - Orchestration
class KodLabApp {
constructor() {
this.compiler = new KodCompiler();
this.emulator = new KodEmulator('gameCanvas');
this.editor = null; // Monaco Editor
this.setupEventListeners();
}
compile() {
const code = this.editor.getValue();
const result = this.compiler.compile(code);
if (result.success) {
this.logMessage('✅ Compilation réussie !', 'success');
this.compiledCode = result.code;
return true;
} else {
this.logMessage('❌ ' + result.error, 'error');
return false;
}
}
run() {
if (!this.compiledCode) {
if (!this.compile()) return;
}
// Chargement dans l'émulateur
const loadResult = this.emulator.loadGame(this.compiledCode);
if (loadResult.success) {
// Démarrage du jeu
const runResult = this.emulator.run();
if (runResult.success) {
this.logMessage('▶️ ' + runResult.message, 'success');
this.updateUI('running');
} else {
this.logMessage('❌ ' + runResult.error, 'error');
}
} else {
this.logMessage('❌ ' + loadResult.error, 'error');
}
}
stop() {
this.emulator.stop();
this.logMessage('⏹️ Jeu arrêté', 'info');
this.updateUI('stopped');
}
}
Gestion des Erreurs
Types d'Erreurs
1. Erreurs de Compilation
// Erreurs syntaxiques
"Erreur ligne 15: fonction 'drawCircl' inconnue"
"Erreur ligne 8: 'end' attendu après 'if'"
"Erreur ligne 22: variable 'speed' non déclarée"
// Erreurs de structure
"Fonction 'start' ou 'update' manquante"
"Bloc non fermé: vérifiez vos 'end'"
2. Erreurs d'Exécution
// Erreurs JavaScript générées
try {
this.gameInstance.update();
} catch (error) {
this.handleRuntimeError(error);
}
handleRuntimeError(error) {
let message = 'Erreur d\'exécution: ';
if (error.name === 'ReferenceError') {
message += 'Variable ou fonction non définie';
} else if (error.name === 'TypeError') {
message += 'Type de données incorrect';
} else {
message += error.message;
}
this.logError(message);
this.stop();
}
Système de Logging
class Logger {
static log(message, type = 'info') {
const console = document.getElementById('consoleOutput');
const timestamp = new Date().toLocaleTimeString();
const entry = document.createElement('div');
entry.className = `console-entry ${type}`;
entry.innerHTML = `<span class="timestamp">[${timestamp}]</span> ${message}`;
console.appendChild(entry);
console.scrollTop = console.scrollHeight;
// Limite le nombre d'entrées
while (console.children.length > 100) {
console.removeChild(console.firstChild);
}
}
static clear() {
document.getElementById('consoleOutput').innerHTML = '';
}
}
Performance et Optimisations
Optimisations du Compilateur
// Cache des expressions compilées
class KodCompiler {
constructor() {
this.expressionCache = new Map();
this.variableRegistry = new Set();
}
compileExpression(expr) {
// Utilisation du cache
if (this.expressionCache.has(expr)) {
return this.expressionCache.get(expr);
}
const compiled = this.doCompileExpression(expr);
this.expressionCache.set(expr, compiled);
return compiled;
}
}
Optimisations de l'Émulateur
// Pool d'objets pour éviter le garbage collection
class ObjectPool {
constructor(createFn, resetFn, initialSize = 10) {
this.createFn = createFn;
this.resetFn = resetFn;
this.available = [];
this.inUse = [];
// Pré-allocation
for (let i = 0; i < initialSize; i++) {
this.available.push(this.createFn());
}
}
acquire() {
let obj = this.available.pop();
if (!obj) {
obj = this.createFn();
}
this.inUse.push(obj);
return obj;
}
release(obj) {
const index = this.inUse.indexOf(obj);
if (index >= 0) {
this.inUse.splice(index, 1);
this.resetFn(obj);
this.available.push(obj);
}
}
}
Optimisations de Rendu
// Batch rendering pour réduire les appels Canvas
class BatchRenderer {
constructor(ctx) {
this.ctx = ctx;
this.batches = new Map();
}
addRect(x, y, w, h, color) {
if (!this.batches.has(color)) {
this.batches.set(color, []);
}
this.batches.get(color).push({type: 'rect', x, y, w, h});
}
flush() {
for (const [color, shapes] of this.batches) {
this.ctx.fillStyle = color;
for (const shape of shapes) {
if (shape.type === 'rect') {
this.ctx.fillRect(shape.x, shape.y, shape.w, shape.h);
}
}
}
this.batches.clear();
}
}
Métriques de Performance
class PerformanceMonitor {
constructor() {
this.frameTime = 0;
this.fps = 0;
this.memoryUsage = 0;
this.drawCalls = 0;
}
startFrame() {
this.frameStartTime = performance.now();
this.drawCalls = 0;
}
endFrame() {
this.frameTime = performance.now() - this.frameStartTime;
this.fps = Math.round(1000 / this.frameTime);
// Collecte mémoire (si disponible)
if (performance.memory) {
this.memoryUsage = Math.round(performance.memory.usedJSHeapSize / 1024 / 1024);
}
}
recordDrawCall() {
this.drawCalls++;
}
getReport() {
return {
fps: this.fps,
frameTime: this.frameTime.toFixed(2),
drawCalls: this.drawCalls,
memory: this.memoryUsage
};
}
}
🔧 Points d'Extension
Ajout de Nouvelles API
// Dans generateGameClass()
// Ajouter de nouvelles fonctions à la classe générée
playSound(soundId) {
// Intégration Web Audio API
if (this.emulator.audioContext) {
// Lecture du son
}
}
saveToStorage(key, value) {
localStorage.setItem('KodLab_' + key, JSON.stringify(value));
}
loadFromStorage(key) {
const data = localStorage.getItem('KodLab_' + key);
return data ? JSON.parse(data) : null;
}
Support de Nouveaux Types
// Extension du système de types
compileExpression(expr) {
// Support des arrays (simulation)
expr = expr.replace(/(\w+)\[(\d+)\]/g, 'this.$1_$2');
// Support des structures
expr = expr.replace(/(\w+)\.(\w+)/g, 'this.$1_$2');
return expr;
}
Cette architecture modulaire permet une maintenance aisée et des extensions futures tout en gardant une complexité maîtrisée pour l'utilisateur final.
Pour aller plus loin, consultez l'API de Référence ! 🚀