189 lines
6.1 KiB
JavaScript
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;
|