update mockdata
All checks were successful
Deploy to Production / deploy (push) Successful in 8s

This commit is contained in:
Đặng Minh Quang
2026-02-25 12:28:30 +07:00
parent 81e7a111be
commit 31dacd0321
6 changed files with 363 additions and 85 deletions

View File

@@ -19347,6 +19347,15 @@ cr.plugins_.SenaPlugin = function (runtime) {
this.bgMusicPlaying = false; this.bgMusicPlaying = false;
this.bgMusicPaused = false; this.bgMusicPaused = false;
this.calculatedPositions = []; this.calculatedPositions = [];
this.customData = {
data1: "",
data2: "",
data3: "",
data4: "",
data5: ""
};
this.lastMessageData = null;
this.lastSenderUUID = "";
}; };
instanceProto.onDestroy = function () { instanceProto.onDestroy = function () {
if (this.sdk) { if (this.sdk) {
@@ -19376,6 +19385,15 @@ cr.plugins_.SenaPlugin = function (runtime) {
Cnds.prototype.OnResume = function () { Cnds.prototype.OnResume = function () {
return true; return true;
}; };
Cnds.prototype.OnPairCorrect = function () {
return true;
};
Cnds.prototype.OnPairWrong = function () {
return true;
};
Cnds.prototype.OnMessage = function () {
return true;
};
pluginProto.cnds = new Cnds(); pluginProto.cnds = new Cnds();
function Acts() {} function Acts() {}
Acts.prototype.Load = function () { Acts.prototype.Load = function () {
@@ -19383,6 +19401,16 @@ cr.plugins_.SenaPlugin = function (runtime) {
var gameCode = this.properties[0] || "G2510S1T30"; var gameCode = this.properties[0] || "G2510S1T30";
if (window["SenaSDK"]) { if (window["SenaSDK"]) {
this.sdk = new window["SenaSDK"](gameCode); this.sdk = new window["SenaSDK"](gameCode);
this.sdk.onCustomMessage = function (data, senderUuid) {
console.log('Runtime received custom message from:', senderUuid);
console.log('Data:', data);
self.lastMessageData = data;
self.lastSenderUUID = senderUuid;
window["SenaTrigger"].runtime.trigger(
cr.plugins_.SenaPlugin.prototype.cnds.OnMessage,
window["SenaTrigger"]
);
};
this.sdk.load(function (success) { this.sdk.load(function (success) {
if (success) { if (success) {
console.log("SDK loaded successfully"); console.log("SDK loaded successfully");
@@ -19506,22 +19534,93 @@ cr.plugins_.SenaPlugin = function (runtime) {
objectWidth, objectWidth,
margin, margin,
maxWidth, maxWidth,
rowBreak,
rowGap,
type,
groupGap
) { ) {
var self = this;
this.calculatedPositions = []; this.calculatedPositions = [];
var totalWidth = count * objectWidth + (count - 1) * margin; if (count <= 0) return;
var startX = (maxWidth - totalWidth) / 2; var rows = [];
for (var i = 0; i < count; i++) { if (rowBreak > 0) {
var posX = startX + i * (objectWidth + margin) + objectWidth / 2; for (var i = 0; i < count; i += rowBreak) {
this.calculatedPositions.push(posX); rows.push(Math.min(rowBreak, count - i));
}
} else {
if (count <= 5) {
rows.push(count);
} else {
var top = Math.ceil((count + 1) / 2);
var bottom = count - top;
rows.push(top);
rows.push(bottom);
}
} }
console.log("Calculated positions:", this.calculatedPositions); var baseY = 0;
if (type === "word") {
baseY = groupGap || (rowGap * rows.length); // word always below slot
}
var index = 0;
for (var r = 0; r < rows.length; r++) {
var itemsInRow = rows[r];
var rowWidth = itemsInRow * objectWidth + (itemsInRow - 1) * margin;
var startX = (maxWidth - rowWidth) / 2;
for (var i = 0; i < itemsInRow; i++) {
this.calculatedPositions.push({
x: startX + i * (objectWidth + margin) + objectWidth / 2,
y: baseY + r * rowGap
});
index++;
}
}
console.log("Calculated positions (multi-row):", this.calculatedPositions);
}; };
Acts.prototype.LoadLevelG5 = function (levelIndex) { Acts.prototype.LoadLevelG5 = function (levelIndex) {
if (this.sdk && this.sdk.loadLevelG5) { if (this.sdk && this.sdk.loadLevelG5) {
this.sdk.loadLevelG5(levelIndex); this.sdk.loadLevelG5(levelIndex);
} }
}; };
Acts.prototype.CheckPair = function (idx1, idx2) {
var self = this;
if (this.sdk && this.sdk.checkPair) {
this.sdk.checkPair(idx1, idx2, function (isMatch) {
if (isMatch) {
window["SenaTrigger"].runtime.trigger(
cr.plugins_.SenaPlugin.prototype.cnds.OnPairCorrect,
window["SenaTrigger"],
);
} else {
window["SenaTrigger"].runtime.trigger(
cr.plugins_.SenaPlugin.prototype.cnds.OnPairWrong,
window["SenaTrigger"],
);
}
});
}
};
Acts.prototype.SetData = function (data1, data2, data3, data4, data5) {
this.customData.data1 = data1 || "";
this.customData.data2 = data2 || "";
this.customData.data3 = data3 || "";
this.customData.data4 = data4 || "";
this.customData.data5 = data5 || "";
console.log('Custom data set:', this.customData);
};
Acts.prototype.PostMessage = function () {
if (this.sdk && this.sdk.sendMessageToParent) {
var dataToSend = {
data1: this.customData.data1,
data2: this.customData.data2,
data3: this.customData.data3,
data4: this.customData.data4,
data5: this.customData.data5
};
this.sdk.sendMessageToParent(dataToSend);
console.log('Posted message to parent:', dataToSend);
} else {
console.error('SDK not initialized or sendMessageToParent not available');
}
};
pluginProto.acts = new Acts(); pluginProto.acts = new Acts();
function Exps() {} function Exps() {}
Exps.prototype.getQuestionValue = function (ret) { Exps.prototype.getQuestionValue = function (ret) {
@@ -19663,12 +19762,15 @@ cr.plugins_.SenaPlugin = function (runtime) {
} }
}; };
Exps.prototype.getPosXbyIndex = function (ret, index) { Exps.prototype.getPosXbyIndex = function (ret, index) {
if ( if (this.calculatedPositions[index]) {
this.calculatedPositions && ret.set_float(this.calculatedPositions[index].x);
index >= 0 && } else {
index < this.calculatedPositions.length ret.set_float(0);
) { }
ret.set_float(this.calculatedPositions[index]); };
Exps.prototype.getPosYbyIndex = function (ret, index) {
if (this.calculatedPositions[index]) {
ret.set_float(this.calculatedPositions[index].y);
} else { } else {
ret.set_float(0); ret.set_float(0);
} }
@@ -19730,7 +19832,36 @@ cr.plugins_.SenaPlugin = function (runtime) {
} }
}; };
Exps.prototype.GetCardType = function (ret, index) { Exps.prototype.GetCardType = function (ret, index) {
ret.set_string(this.sdk.getCardType(index)); if (this.sdk && this.sdk.getCardType) {
ret.set_string(this.sdk.getCardType(index));
} else {
ret.set_string("");
}
};
Exps.prototype.getCardID = function (ret, index) {
if (this.sdk && this.sdk.getCardID) {
ret.set_string(this.sdk.getCardID(index) || "");
} else {
ret.set_string("");
}
};
Exps.prototype.getData = function (ret, dataIndex) {
var dataKey = "data" + dataIndex;
if (this.customData && this.customData[dataKey] !== undefined) {
ret.set_string(this.customData[dataKey]);
} else {
ret.set_string("");
}
};
Exps.prototype.getLastSenderUUID = function (ret) {
ret.set_string(this.lastSenderUUID || "");
};
Exps.prototype.getLastMessageJSON = function (ret) {
if (this.lastMessageData) {
ret.set_string(JSON.stringify(this.lastMessageData));
} else {
ret.set_string("{}");
}
}; };
pluginProto.exps = new Exps(); pluginProto.exps = new Exps();
})(); })();
@@ -25638,13 +25769,13 @@ cr.behaviors.lunarray_Tween = function(runtime)
}; };
}()); }());
cr.getObjectRefTable = function () { return [ cr.getObjectRefTable = function () { return [
cr.plugins_.Audio,
cr.plugins_.Browser, cr.plugins_.Browser,
cr.plugins_.Function, cr.plugins_.Function,
cr.plugins_.Sprite,
cr.plugins_.SenaPlugin, cr.plugins_.SenaPlugin,
cr.plugins_.Sprite,
cr.plugins_.Text, cr.plugins_.Text,
cr.plugins_.Touch, cr.plugins_.Touch,
cr.plugins_.Audio,
cr.behaviors.Rex_MoveTo, cr.behaviors.Rex_MoveTo,
cr.behaviors.Sin, cr.behaviors.Sin,
cr.behaviors.Timer, cr.behaviors.Timer,

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
{ {
"version": 1770361843, "version": 1771997214,
"fileList": [ "fileList": [
"data.js", "data.js",
"c2runtime.js", "c2runtime.js",

View File

@@ -179,6 +179,9 @@ SenaSDK.prototype.loadFromPostMessage = function (inputJson, callback) {
self.currentQuestion = self.list[0]; self.currentQuestion = self.list[0];
self.userResults = []; self.userResults = [];
// [UPDATE G4] Process G4 Data
if (self.gameType === 4) self._processG4Data();
console.log( console.log(
"✅ Sena SDK: Single question loaded -", "✅ Sena SDK: Single question loaded -",
inputJson.description || inputJson.data.question, inputJson.description || inputJson.data.question,
@@ -247,7 +250,7 @@ SenaSDK.prototype.loadFromPostMessage = function (inputJson, callback) {
*/ */
SenaSDK.prototype._parseGameCode = function () { SenaSDK.prototype._parseGameCode = function () {
let self = this; let self = this;
const gameCode = self.gameCode || "G5610S1T5"; // Mặc định mẫu G5 const gameCode = self.gameCode || "G4410S1T30"; // G4 mẫu
// Regex hỗ trợ G1-G9 // Regex hỗ trợ G1-G9
const regex = /^G([1-9])(\d{1,2})([0-2])([0-2])(?:S([0-1]))?(?:T(\d+))?$/; const regex = /^G([1-9])(\d{1,2})([0-2])([0-2])(?:S([0-1]))?(?:T(\d+))?$/;
@@ -515,6 +518,9 @@ SenaSDK.prototype._loadFromServer = function (
self.loadLevelG5(1); self.loadLevelG5(1);
} }
// [UPDATE G4] Process G4 Data
if (self.gameType === 4) self._processG4Data();
console.log("🎮 Sena SDK: Data loaded for", self.gameCode); console.log("🎮 Sena SDK: Data loaded for", self.gameCode);
self._dataLoadedFromServer = true; self._dataLoadedFromServer = true;
if (callback) callback(true); if (callback) callback(true);
@@ -872,14 +878,12 @@ SenaSDK.prototype.guide = function () {
return guide; return guide;
}; };
/** /**
* Get the question text * Get the question text/url
* @returns {string} Question or request text * @returns {string} Question, request text, or URL
*/ */
SenaSDK.prototype.getQuestionValue = function () { SenaSDK.prototype.getQuestionValue = function () {
var q = String(this.data.question || "").trim(); var q = String(this.data.question || "").trim();
if (q.toLowerCase().startsWith("http")) { // Đã bỏ chặn URL để có thể lấy link ảnh/audio
return "";
}
return q; return q;
}; };
/** /**
@@ -1072,8 +1076,7 @@ SenaSDK.prototype.canReloadData = function () {
/** /**
* End the game and check answer * End the game and check answer
* @param {string} answer - User's answer (single text or multiple answers separated by |) * [UPDATE] Support Unordered Answers & Auto-cleanup empty strings
* @returns {Object} Result object with isCorrect, duration, correctAnswer, and userAnswer
*/ */
SenaSDK.prototype.end = function (answer, callback) { SenaSDK.prototype.end = function (answer, callback) {
let self = this; let self = this;
@@ -1106,88 +1109,93 @@ SenaSDK.prototype.end = function (answer, callback) {
self.endTime = Date.now(); self.endTime = Date.now();
const duration = (self.endTime - self.startTime) / 1000; const duration = (self.endTime - self.startTime) / 1000;
// Parse user answer - split by | for multiple answers // 1. CLEANUP INPUT: Tách chuỗi, Xóa khoảng trắng, Chuyển thường, LỌC BỎ RỖNG
// .filter(a => a) sẽ loại bỏ ngay cái đuôi "" do dấu | thừa tạo ra
const userAnswers = answer.includes("|") const userAnswers = answer.includes("|")
? answer.split("|").map((a) => a.trim().toLowerCase()) ? answer
: [answer.trim().toLowerCase()]; .split("|")
.map((a) => a.trim().toLowerCase())
.filter((a) => a)
: [answer.trim().toLowerCase()].filter((a) => a);
// Get correct answer(s) from data // 2. GET CORRECT ANSWERS
let correctAnswers = []; let correctAnswers = [];
if (self.correctAnswer) { if (self.correctAnswer) {
// Check if answer is an array (multiple answers) or single answer
if (Array.isArray(self.correctAnswer)) { if (Array.isArray(self.correctAnswer)) {
correctAnswers = self.correctAnswer.map((a) => { correctAnswers = self.correctAnswer.map((a) =>
if (typeof a === "string") return a.toLowerCase(); (typeof a === "object" ? a.text || "" : String(a)).trim().toLowerCase(),
if (a.text) return a.text.toLowerCase(); );
return ""; } else {
}); let str =
} else if (typeof self.correctAnswer === "string") { typeof self.correctAnswer === "object"
correctAnswers = [self.correctAnswer.toLowerCase()]; ? self.correctAnswer.text
} else if (self.correctAnswer.text) { : String(self.correctAnswer);
correctAnswers = [self.correctAnswer.text.toLowerCase()]; correctAnswers = str.includes("|")
? str.split("|").map((a) => a.trim().toLowerCase())
: [str.trim().toLowerCase()];
} }
} }
// Check if answer is correct // 3. COMPARE
// Nếu là Game Type 2 (Sort) thì giữ nguyên thứ tự, nếu không thì sort (unordered)
const isStrictOrder = self.gameType === 2;
const finalUser = isStrictOrder ? [...userAnswers] : [...userAnswers].sort();
const finalCorrect = isStrictOrder
? [...correctAnswers]
: [...correctAnswers].sort();
let isCorrect = false; let isCorrect = false;
// Helper to normalize and strip proxy from URL // Helper check file name for URL matching
const normalizeAnswer = (str) => {
let val = String(str || "")
.trim()
.toLowerCase();
if (val.includes("corsproxy.io/?")) {
try {
let decoded = decodeURIComponent(val.split("corsproxy.io/?")[1]);
if (decoded) val = decoded.toLowerCase().trim();
} catch (e) {}
}
return val;
};
// Helper to get filename from URL
const getFileName = (url) => { const getFileName = (url) => {
if (!url.startsWith("http")) return url; if (!url.startsWith("http")) return url;
const parts = url.split("/"); try {
return parts[parts.length - 1].split("?")[0]; return url.split("/").pop().split("?")[0];
} catch (e) {
return url;
}
}; };
const normUser = userAnswers.map(normalizeAnswer); if (finalUser.length === finalCorrect.length) {
const normCorrect = correctAnswers.map(normalizeAnswer); isCorrect = finalUser.every((uVal, index) => {
let cVal = finalCorrect[index];
if (normUser.length === normCorrect.length) { if (uVal === cVal) return true;
// For ordered multiple answers // Fuzzy match cho URL (so sánh tên file ảnh)
isCorrect = normUser.every((ans, index) => { if (uVal.startsWith("http") || cVal.startsWith("http")) {
if (ans === normCorrect[index]) return true; return getFileName(uVal) === getFileName(cVal);
// Fuzzy match for URLs
if (ans.startsWith("http") || normCorrect[index].startsWith("http")) {
return getFileName(ans) === getFileName(normCorrect[index]);
} }
return false; return false;
}); });
} else if (normUser.length === 1 && normCorrect.length === 1) {
// For single answer
const u = normUser[0];
const c = normCorrect[0];
isCorrect = u === c;
if (!isCorrect && (u.startsWith("http") || c.startsWith("http"))) {
isCorrect = getFileName(u) === getFileName(c);
}
} }
// -----------------------------------------------------------
// [BƯỚC 1] Kiểm tra Time Limit TRƯỚC (Sửa biến isCorrect)
// -----------------------------------------------------------
if (self.timeLimit > 0 && duration > self.timeLimit) {
isCorrect = false; // CHỈ sửa biến boolean, KHÔNG gọi result.isCorrect
console.log("🎮 Sena SDK: Time Limit Exceeded -> Result set to False");
}
// -----------------------------------------------------------
// [BƯỚC 2] Sau đó mới tạo biến result (Dùng isCorrect đã chốt)
// -----------------------------------------------------------
const result = { const result = {
isCorrect: isCorrect, isCorrect: isCorrect, // Lúc này isCorrect đã được xử lý xong xuôi
duration: duration, duration: duration,
correctAnswer: correctAnswers.join(" | "), correctAnswer: correctAnswers.join(" | "),
userAnswer: userAnswers.join(" | "), userAnswer: userAnswers.join(" | "),
}; };
// if time spent more than time limit, mark as incorrect
if (self.timeLimit > 0 && duration > self.timeLimit) { // -----------------------------------------------------------
result.isCorrect = false; // [BƯỚC 3] Log và Return
} // -----------------------------------------------------------
console.log(`Time spent in game: ${duration} seconds`); console.log(`Time spent: ${duration}s`);
console.log(`Result: ${isCorrect ? "CORRECT" : "INCORRECT"}`); console.log(
`Result: ${isCorrect ? "CORRECT" : "INCORRECT"} (User: ${result.userAnswer} vs Correct: ${result.correctAnswer})`,
);
if (callback) callback(result.isCorrect); if (callback) callback(result.isCorrect);
return result; // Return full object for debug
}; };
SenaSDK.prototype.playVoice = function (type) { SenaSDK.prototype.playVoice = function (type) {
@@ -1366,6 +1374,17 @@ SenaSDK.prototype.registerPostMessageListener = function () {
} }
break; break;
case "SEQUENCE_SYNC":
console.log("📥 Sena SDK: Received SEQUENCE_SYNC", event.data);
if (event.data.uuid === self.uuid) {
console.log("🔄 Sena SDK: Own message echoed back, processing...");
}
if (typeof self.onCustomMessage === "function") {
self.onCustomMessage(event.data.data, event.data.uuid);
}
break;
case "SDK_ERROR": case "SDK_ERROR":
// Server gửi error // Server gửi error
console.error("❌ Sena SDK: Received SDK_ERROR", event.data.payload); console.error("❌ Sena SDK: Received SDK_ERROR", event.data.payload);
@@ -1744,9 +1763,137 @@ SenaSDK.prototype.getTimePerCard = function () {
}; };
SenaSDK.prototype.getCardType = function (index) { SenaSDK.prototype.getCardType = function (index) {
if (!this.masterList || !this.masterList[index]) return "text"; // Default an toàn // Ưu tiên 1: Lấy từ data.options (G4, G1, G2 đang chạy trên grid hiện tại)
return this.masterList[index].type || "text"; if (this.data && this.data.options && this.data.options[index]) {
return this.data.options[index].type || "text";
}
// Ưu tiên 2: Fallback cho G5 (Master List)
if (this.masterList && this.masterList[index]) {
return this.masterList[index].type || "text";
}
// Mặc định
return "text";
}; };
// [UPDATE G4] Xử lý data đặc thù cho Memory Card: Fill blank và Xử lý thẻ lẻ (Orphan)
SenaSDK.prototype._processG4Data = function () {
let self = this;
if (!self.data.options) self.data.options = [];
// BƯỚC 1: Xử lý thẻ lẻ (Sanitize Data) ngay tại nguồn
// Đếm số lượng xuất hiện của từng cặp tên
let counts = {};
self.data.options.forEach((item) => {
if (item.type !== "blank" && item.name) {
counts[item.name] = (counts[item.name] || 0) + 1;
}
});
// Duyệt lại và biến những thẻ có số lượng < 2 thành blank
self.data.options.forEach((item) => {
if (item.type !== "blank" && item.name) {
if (counts[item.name] < 2) {
console.log("🎮 Sena SDK: Orphan card detected & removed:", item.name);
item.type = "blank";
item.name = "blank"; // Xóa tên để tránh logic game bắt nhầm
item.image = ""; // Xóa ảnh
item.id = "blank_sanitized";
}
}
});
// BƯỚC 2: Fill thêm thẻ blank cho đủ 9 ô (Logic cũ)
while (self.data.options.length < 9) {
self.data.options.push({
id: "blank_" + self.data.options.length,
type: "blank",
name: "blank",
value: -1,
image: "",
});
}
// BƯỚC 3: Shuffle (Trộn bài)
if (self.shuffle) {
self.shuffleArray(self.data.options);
}
};
// [UPDATE G4] Hàm lấy ID
SenaSDK.prototype.getCardID = function (index) {
if (this.data && this.data.options && this.data.options[index]) {
return this.data.options[index].id || "";
}
return "";
};
// [UPDATE G4] Hàm Check Pair (Logic tạm thời ở Client cho Mock)
SenaSDK.prototype.checkPair = function (idx1, idx2, callback) {
let self = this;
// Validate index
let card1 = self.data.options[idx1];
let card2 = self.data.options[idx2];
if (!card1 || !card2) {
if (callback) callback(false);
return;
}
// Logic so sánh: Name giống nhau VÀ ID khác nhau (tránh click 2 lần 1 thẻ) VÀ không phải blank
let isMatch = false;
if (card1.type !== "blank" && card2.type !== "blank") {
if (card1.id !== card2.id) {
// Check ko phải chính nó
// So sánh name (ví dụ: "dog" == "dog")
if (
card1.name &&
card2.name &&
card1.name.toLowerCase() === card2.name.toLowerCase()
) {
isMatch = true;
}
}
}
console.log(
`🎮 Sena SDK: Check Pair [${idx1}] vs [${idx2}] -> ${isMatch ? "MATCH" : "WRONG"}`,
);
// [TODO] Sau này sẽ thay đoạn này bằng postMessage lên Server verify
if (callback) callback(isMatch);
};
/**
* [NEW v2.2] Gửi Custom Data lên Parent Window
* @param {Object} data - Object chứa 5 trường data1 -> data5
*/
SenaSDK.prototype.sendMessageToParent = function (data) {
let self = this;
// Tự động tạo UUID cho session nếu chưa có
if (!self.uuid) {
self.uuid =
"session-" + Date.now() + "-" + Math.floor(Math.random() * 10000);
}
// Đóng gói payload đúng chuẩn tài liệu v2.2
let payload = {
type: "SEQUENCE_SYNC",
uuid: self.uuid,
data: data,
timestamp: Date.now(),
};
console.log("📤 Sena SDK: Sending SEQUENCE_SYNC to parent:", payload);
// Gửi lên Parent Window (Backend/Iframe parent)
window.parent.postMessage(payload, "*");
};
if (typeof module !== "undefined" && module.exports) { if (typeof module !== "undefined" && module.exports) {
module.exports = SenaSDK; module.exports = SenaSDK;
} else if (typeof define === "function" && define.amd) { } else if (typeof define === "function" && define.amd) {

Binary file not shown.

Binary file not shown.