Files
SQ_Word_Hint-Audio/SQ_Word_Hint-Audio/tdv_view.js
Đặng Minh Quang 5730725188 Initial commit
2026-01-27 16:43:18 +07:00

189 lines
6.1 KiB
JavaScript

/**
* TDV_VIEW.js - V2: Layout Refinement
* Nhiệm vụ: Tinh chỉnh khoảng cách, font size và vị trí spawn theo feedback.
*/
var TDV_View = {
config: {
canvasW: 1200,
// --- CẤU HÌNH SLOT (Ô CHỮ) ---
slotBaseY: 700, // Đẩy thấp xuống thêm chút nữa cho thoáng mây
slotSize: 130, // Kích thước logic
// 🔥 THAY ĐỔI QUAN TRỌNG TẠI ĐÂY:
slotSpacingX: 100, // Tăng gấp đôi (cũ 30) để tách các viên thuốc
slotSpacingY: 25, // Tăng nhẹ khoảng cách dòng (cũ 15)
maxCol: 5, // 5 ô/hàng
slotFontSize: 50, // Font slot giữ nguyên
// --- CẤU HÌNH ANSWER (MỚI: FLEX LAYOUT) ---
cloudCenterY: 450, // Tâm Y của đám mây
cloudWidth: 800, // Chiều rộng vùng khả dụng trong mây
answerItemW: 200, // Chiều rộng ước lượng của 1 viên thuốc answer
answerItemH: 85, // Chiều cao ước lượng
answerGapX: 20, // Khoảng cách ngang giữa các answer
answerGapY: 20, // Khoảng cách dọc giữa các dòng answer
answerFontSize: 36,
},
// Lưu trữ vị trí đã tính toán
slotPositions: [],
answerPositions: [],
currentScale: 1,
/**
* TÍNH TOÁN LAYOUT
*/
calculateLayout: function (itemCount) {
if (!itemCount) return;
this.slotPositions = [];
this.answerPositions = [];
const cfg = this.config;
// ================= 1. TÍNH SLOT (GRID) =================
// Tính scale nếu quá nhiều từ
var rowsNeeded = Math.ceil(itemCount / cfg.maxCol);
this.currentScale = 1;
if (rowsNeeded > 2) this.currentScale = 0.9;
var size = cfg.slotSize * this.currentScale;
var spaceX = cfg.slotSpacingX * this.currentScale;
var spaceY = cfg.slotSpacingY * this.currentScale;
for (var i = 0; i < itemCount; i++) {
var row = Math.floor(i / cfg.maxCol);
// Tính số item trong dòng hiện tại để căn giữa
var itemsInThisRow = cfg.maxCol;
if (row === rowsNeeded - 1) {
itemsInThisRow = itemCount % cfg.maxCol || cfg.maxCol;
}
var colIndex = i % cfg.maxCol;
// Tính Width của cả dòng để căn giữa màn hình
var rowWidth = itemsInThisRow * size + (itemsInThisRow - 1) * spaceX;
var rowStartX = (cfg.canvasW - rowWidth) / 2 + size / 2;
var posX = rowStartX + colIndex * (size + spaceX);
// posY tính toán riêng biệt cho từng dòng
var posY = cfg.slotBaseY + row * (size + spaceY);
this.slotPositions.push({ x: posX, y: posY });
}
// ================= 2. TÍNH ANSWER (FLEX LAYOUT) =================
// Logic: Xếp hàng ngang, tự xuống dòng, căn giữa từng dòng.
var answerCount = 0;
if (window.TDV_Core) answerCount = TDV_Core.getMissingCharsCount();
if (answerCount === 0) answerCount = itemCount; // Fallback
// Tính số lượng tối đa trên 1 hàng dựa vào chiều rộng mây
var itemTotalW = cfg.answerItemW * this.currentScale;
var itemTotalH = cfg.answerItemH * this.currentScale;
var gapX = cfg.answerGapX;
var gapY = cfg.answerGapY;
// Tính xem 1 hàng nhét được bao nhiêu viên
var maxPerLine = Math.floor(cfg.cloudWidth / (itemTotalW + gapX));
if (maxPerLine < 1) maxPerLine = 1;
// Tính tổng số dòng cần thiết
var totalLines = Math.ceil(answerCount / maxPerLine);
// Tính chiều cao tổng của khối answer để căn giữa theo chiều dọc (Center Y)
var totalBlockHeight = totalLines * itemTotalH + (totalLines - 1) * gapY;
var startBlockY = cfg.cloudCenterY - totalBlockHeight / 2 + itemTotalH / 2;
for (var i = 0; i < answerCount; i++) {
var lineIndex = Math.floor(i / maxPerLine); // Đang ở dòng mấy
// Tính số item trong dòng hiện tại (để căn giữa dòng đó)
var itemsInCurrentLine = maxPerLine;
// Nếu là dòng cuối cùng
if (lineIndex === totalLines - 1) {
itemsInCurrentLine = answerCount % maxPerLine || maxPerLine;
}
// Index trong dòng (0, 1, 2...)
var indexInLine = i % maxPerLine;
// Tính chiều rộng dòng hiện tại
var currentLineWidth =
itemsInCurrentLine * itemTotalW + (itemsInCurrentLine - 1) * gapX;
var startLineX = (cfg.canvasW - currentLineWidth) / 2 + itemTotalW / 2;
var ansX = startLineX + indexInLine * (itemTotalW + gapX);
var ansY = startBlockY + lineIndex * (itemTotalH + gapY);
this.answerPositions.push({
x: ansX,
y: ansY,
angle: 0,
});
}
console.log(`📏 VIEW V4: Flex Layout Applied. Answers: ${answerCount}`);
},
// --- GETTERS (Giữ nguyên logic gọi) ---
getSlotX: function (i) {
return this.slotPositions[i] ? this.slotPositions[i].x : 0;
},
getSlotY: function (i) {
return this.slotPositions[i] ? this.slotPositions[i].y : 0;
},
getAnswerX: function (i) {
return this.answerPositions[i] ? this.answerPositions[i].x : 0;
},
getAnswerY: function (i) {
return this.answerPositions[i] ? this.answerPositions[i].y : 0;
},
getAnswerAngle: function (i) {
return 0;
}, // Luôn trả về 0 độ
getScale: function () {
return this.currentScale;
},
// Font size riêng biệt
getFontSize: function () {
return Math.floor(this.config.slotFontSize * this.currentScale);
},
getAnswerFontSize: function () {
return Math.floor(this.config.answerFontSize * this.currentScale);
},
// Text Content Helpers
getDisplayText: function (i) {
if (window.TDV_Core && TDV_Core.questionTemplate) {
if (!TDV_Core.isBlankIndex(i)) return TDV_Core.questionTemplate[i];
return TDV_Core.placedChars[i] || "";
}
return "";
},
getAnswerText: function (i) {
if (window.TDV_Core && TDV_Core.missingChars) {
return TDV_Core.missingChars[i] || "";
}
return "";
},
shuffleArray: function (array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
},
};
window.TDV_View = TDV_View;