From 8f84a178134a3f4b3deef15981c8a16dd1b6b811 Mon Sep 17 00:00:00 2001 From: Grzegorz Kucmierz Date: Tue, 10 Feb 2026 23:00:43 +0100 Subject: [PATCH] feat: add difficulty and dirty flag to result image --- src/components/CustomGameModal.vue | 5 +++++ src/components/WinModal.vue | 31 +++++++++++++++++++++++++++++- src/composables/useI18n.js | 4 ++++ src/stores/puzzle.js | 17 ++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/components/CustomGameModal.vue b/src/components/CustomGameModal.vue index 9c0fa39..108e18c 100644 --- a/src/components/CustomGameModal.vue +++ b/src/components/CustomGameModal.vue @@ -213,6 +213,8 @@ input[type="range"]::-moz-range-thumb { justify-content: center; gap: 10px; align-items: center; + white-space: nowrap; + height: 1.5em; /* Reserve space for one line of text */ } .difficulty-indicator .label { @@ -224,6 +226,9 @@ input[type="range"]::-moz-range-thumb { text-transform: uppercase; text-shadow: 0 0 10px currentColor; transition: color 0.3s ease; + display: inline-block; + min-width: 120px; /* Reserve space for longest text */ + text-align: left; } .error { diff --git a/src/components/WinModal.vue b/src/components/WinModal.vue index 6af297e..f2ddc49 100644 --- a/src/components/WinModal.vue +++ b/src/components/WinModal.vue @@ -88,8 +88,9 @@ const buildShareCanvas = () => { const padding = 28; const headerHeight = 64; const footerHeight = 28; + const infoHeight = 40; // New space for difficulty/guide info const width = boardSize + padding * 2; - const height = boardSize + padding * 2 + headerHeight + footerHeight; + const height = boardSize + padding * 2 + headerHeight + footerHeight + infoHeight; const scale = window.devicePixelRatio || 1; const canvas = document.createElement('canvas'); canvas.width = width * scale; @@ -109,6 +110,25 @@ const buildShareCanvas = () => { ctx.fillText(t('app.title'), padding, padding + 10); ctx.font = '600 16px "Segoe UI", sans-serif'; ctx.fillText(`${t('win.time')} ${formattedTime.value}`, padding, padding + 34); + + // Difficulty & Density Info + const densityPercent = Math.round(store.currentDensity * 100); + const dist = Math.abs(densityPercent - 50); + let difficultyKey = 'easy'; + let diffColor = '#33ff33'; + if (dist <= 5) { difficultyKey = 'extreme'; diffColor = '#ff3333'; } + else if (dist <= 15) { difficultyKey = 'hardest'; diffColor = '#ff9933'; } + else if (dist <= 25) { difficultyKey = 'harder'; diffColor = '#ffff33'; } + + const difficultyText = t(`difficulty.${difficultyKey}`); + ctx.font = '600 14px "Segoe UI", sans-serif'; + + // Right aligned difficulty info + const diffLabel = `${t('win.difficulty')} ${difficultyText} (${densityPercent}%)`; + const diffWidth = ctx.measureText(diffLabel).width; + ctx.fillStyle = diffColor; + ctx.fillText(diffLabel, width - padding - diffWidth, padding + 34); + const gridX = padding; const gridY = padding + headerHeight; ctx.fillStyle = 'rgba(255, 255, 255, 0.06)'; @@ -152,6 +172,15 @@ const buildShareCanvas = () => { } } } + + // Guide Usage Info (Dirty Flag) + if (store.guideUsageCount > 0) { + ctx.fillStyle = '#ff4d4d'; + ctx.font = '600 14px "Segoe UI", sans-serif'; + const guideText = t('win.usedGuide', { count: store.guideUsageCount }); + ctx.fillText(`⚠️ ${guideText}`, padding, height - padding - footerHeight + 10); + } + ctx.fillStyle = 'rgba(255, 255, 255, 0.75)'; ctx.font = '500 14px "Segoe UI", sans-serif'; ctx.fillText(appUrl, padding, height - padding + 6); diff --git a/src/composables/useI18n.js b/src/composables/useI18n.js index 3216ac7..e09dbfc 100644 --- a/src/composables/useI18n.js +++ b/src/composables/useI18n.js @@ -45,6 +45,8 @@ const messages = { 'win.shareFacebook': 'Facebook', 'win.shareWhatsapp': 'WhatsApp', 'win.shareDownload': 'Pobierz zrzut', + 'win.difficulty': 'Poziom:', + 'win.usedGuide': 'Użyto podpowiedzi: {count}', 'pwa.installTitle': 'Zainstaluj aplikację i graj offline', 'pwa.installMobile': 'Dodaj do ekranu głównego', 'pwa.installDesktop': 'Zainstaluj na komputerze', @@ -150,6 +152,8 @@ const messages = { 'win.shareFacebook': 'Facebook', 'win.shareWhatsapp': 'WhatsApp', 'win.shareDownload': 'Download screenshot', + 'win.difficulty': 'Difficulty:', + 'win.usedGuide': 'Guide used: {count}', 'pwa.installTitle': 'Install the app and play offline', 'pwa.installMobile': 'Add to home screen', 'pwa.installDesktop': 'Install on desktop', diff --git a/src/stores/puzzle.js b/src/stores/puzzle.js index c81400b..50c5779 100644 --- a/src/stores/puzzle.js +++ b/src/stores/puzzle.js @@ -64,6 +64,9 @@ export const usePuzzleStore = defineStore('puzzle', () => { const playerGrid = ref([]); // 0: empty, 1: filled, 2: cross const isGameWon = ref(false); const hasUsedGuide = ref(false); + const guideUsageCount = ref(0); + const currentDifficulty = ref(null); // 'easy', 'medium', 'hard', 'custom' or object { density: 0.5 } + const currentDensity = ref(0); const size = ref(5); const startTime = ref(null); const elapsedTime = ref(0); @@ -118,6 +121,8 @@ export const usePuzzleStore = defineStore('puzzle', () => { resetGrid(); isGameWon.value = false; hasUsedGuide.value = false; + guideUsageCount.value = 0; + currentDensity.value = totalCellsToFill.value / (size.value * size.value); elapsedTime.value = 0; startTimer(); saveState(); @@ -134,8 +139,11 @@ export const usePuzzleStore = defineStore('puzzle', () => { resetGrid(); isGameWon.value = false; hasUsedGuide.value = false; + guideUsageCount.value = 0; + currentDensity.value = density; elapsedTime.value = 0; startTimer(); + saveState(); } function resetGrid() { @@ -243,6 +251,8 @@ export const usePuzzleStore = defineStore('puzzle', () => { playerGrid: playerGrid.value, isGameWon: isGameWon.value, hasUsedGuide: hasUsedGuide.value, + guideUsageCount: guideUsageCount.value, + currentDensity: currentDensity.value, elapsedTime: elapsedTime.value, moves: moves.value, history: history.value @@ -260,6 +270,9 @@ export const usePuzzleStore = defineStore('puzzle', () => { solution.value = parsed.solution; playerGrid.value = parsed.playerGrid; isGameWon.value = parsed.isGameWon; + hasUsedGuide.value = parsed.hasUsedGuide || false; + guideUsageCount.value = parsed.guideUsageCount || 0; + currentDensity.value = parsed.currentDensity || 0; elapsedTime.value = parsed.elapsedTime || 0; moves.value = parsed.moves || 0; history.value = parsed.history || []; @@ -287,6 +300,7 @@ export const usePuzzleStore = defineStore('puzzle', () => { resetGrid(); isGameWon.value = false; hasUsedGuide.value = false; + guideUsageCount.value = 0; elapsedTime.value = 0; startTimer(); saveState(); @@ -298,6 +312,7 @@ export const usePuzzleStore = defineStore('puzzle', () => { function markGuideUsed() { if (isGameWon.value) return; hasUsedGuide.value = true; + guideUsageCount.value++; saveState(); } @@ -326,6 +341,8 @@ export const usePuzzleStore = defineStore('puzzle', () => { undo, closeWinModal, hasUsedGuide, + guideUsageCount, + currentDensity, markGuideUsed };