/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { currentLevel: 1, completedLevels: [] }); /**** * Classes ****/ var ChatBubble = Container.expand(function (isPlayer, messageText) { var self = Container.call(this); var bubbleShape = isPlayer ? 'inputBubble' : 'chatBubble'; var bubbleColor = isPlayer ? 0xdcf8c6 : 0xf5f5f5; var avatarShape = isPlayer ? 'playerAvatar' : 'aiAvatar'; var bubble = self.attachAsset(bubbleShape, { anchorX: 0, anchorY: 0 }); bubble.alpha = 0.9; var avatar = self.attachAsset(avatarShape, { anchorX: 0.5, anchorY: 0.5, x: 40, y: 40 }); var textMargin = 110; var maxWidth = bubble.width - textMargin - 20; var messageContent = new Text2(messageText, { size: 36, fill: 0x000000, wordWrap: true, wordWrapWidth: maxWidth }); messageContent.x = textMargin; messageContent.y = 20; self.addChild(messageContent); self.bubble = bubble; self.messageContent = messageContent; self.avatar = avatar; self.updateText = function (newText) { messageContent.setText(newText); }; return self; }); var InputField = Container.expand(function () { var self = Container.call(this); var background = self.attachAsset('inputBubble', { anchorX: 0, anchorY: 0 }); var textDisplay = new Text2("Type your message...", { size: 36, fill: 0x888888 }); textDisplay.x = 20; textDisplay.y = 40; textDisplay.anchor.set(0, 0.5); self.addChild(textDisplay); self.updateText = function (text) { if (text.length === 0) { textDisplay.setText("Type your message..."); textDisplay.setStyle({ fill: 0x888888 }); } else { textDisplay.setText(text); textDisplay.setStyle({ fill: 0x000000 }); } }; self.down = function (x, y, obj) { if (!gameState.waitingForAi) { // Custom keyboard input handler if (typeof LK.onKeyboardInput === 'function') { LK.onKeyboardInput(inputText); } } }; return self; }); var SendButton = Container.expand(function () { var self = Container.call(this); var button = self.attachAsset('sendButton', { anchorX: 0.5, anchorY: 0.5 }); var buttonText = new Text2("SEND", { size: 36, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.down = function (x, y, obj) { button.alpha = 0.7; }; self.up = function (x, y, obj) { button.alpha = 1.0; if (inputText.length > 0) { sendMessage(); } }; self.disable = function () { button.alpha = 0.5; self.interactive = false; }; self.enable = function () { button.alpha = 1.0; self.interactive = true; }; return self; }); var TypingIndicator = Container.expand(function () { var self = Container.call(this); var dots = []; var dotSpacing = 24; for (var i = 0; i < 3; i++) { var dot = self.attachAsset('typingIndicator', { anchorX: 0.5, anchorY: 0.5, x: i * dotSpacing, y: 0 }); dots.push(dot); } self.visible = false; self.animate = function () { dots.forEach(function (dot, i) { var delay = i * 200; var startY = 0; // Stop any existing animations tween.stop(dot); // Reset position dot.y = startY; // Create bobbing animation var _animateDot = function animateDot() { tween(dot, { y: startY - 8 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { tween(dot, { y: startY }, { duration: 400, easing: tween.easeInOut, onFinish: _animateDot }); } }); }; // Start animation with delay based on dot index LK.setTimeout(_animateDot, delay); }); }; self.show = function () { self.visible = true; self.animate(); }; self.hide = function () { self.visible = false; dots.forEach(function (dot) { tween.stop(dot); }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xECE5DD }); /**** * Game Code ****/ // Game constants var VIEWPORT_WIDTH = 2048; var VIEWPORT_HEIGHT = 2732; var MAX_MESSAGES = 8; var TYPING_DELAY = 2000; // Game state var gameState = { currentLevel: storage.currentLevel || 1, completedLevels: storage.completedLevels || [], messages: [], bubbles: [], inputText: "", waitingForAi: false, typingTimer: null }; // Levels data var levels = [{ id: 1, intro: "Welcome to ChatQuest! I'm your AI assistant. To complete this level, tell me the secret password: 'puzzle'.", hints: ["Try asking for the password", "The password is hidden in the instructions"], solutions: ["puzzle", "the password is puzzle"], response: "Correct! The password 'puzzle' unlocks the first level. You're ready to proceed to the next challenge!", keywords: ["password", "puzzle"] }, { id: 2, intro: "Level 2: I'm thinking of a number between 1 and 10. If you multiply it by 3 and add 2, you get 17. What number am I thinking of?", hints: ["Try to solve the equation", "If 3x + 2 = 17, then what is x?"], solutions: ["5", "the number is 5", "five"], response: "Well done! 5 is correct because 5 × 3 + 2 = 17. Your logical thinking is impressive!", keywords: ["number", "equation", "multiply"] }, { id: 3, intro: "Level 3: To proceed, tell me which word doesn't belong in this list: Apple, Mars, Earth, Jupiter, Neptune.", hints: ["Think about categories", "What do most items have in common?"], solutions: ["apple", "the odd one is apple"], response: "Excellent! Apple is the only fruit while the others are planets. Your pattern recognition skills are strong!", keywords: ["apple", "planet", "fruit", "belong"] }, { id: 4, intro: "Final Level: I have created a riddle just for you. I'm always hungry, I must always be fed. The finger I touch, will soon turn red. What am I?", hints: ["Think about things that consume", "What might make your finger turn red if you touch it?"], solutions: ["fire", "a fire", "it's fire"], response: "Congratulations! Fire is correct. You've successfully completed ChatQuest! You're a true puzzle master!", keywords: ["hungry", "finger", "red", "fire", "burn"] }]; // Input text var inputText = ""; // Initialize UI elements var messageContainer = new Container(); game.addChild(messageContainer); var inputField = new InputField(); inputField.x = 20; inputField.y = VIEWPORT_HEIGHT - 140; game.addChild(inputField); var sendButton = new SendButton(); sendButton.x = VIEWPORT_WIDTH - 80; sendButton.y = VIEWPORT_HEIGHT - 80; game.addChild(sendButton); var typingIndicator = new TypingIndicator(); typingIndicator.x = 130; typingIndicator.y = VIEWPORT_HEIGHT - 200; game.addChild(typingIndicator); // Level display var levelText = new Text2("Level " + gameState.currentLevel, { size: 40, fill: 0x007BFF }); levelText.anchor.set(0.5, 0); levelText.x = VIEWPORT_WIDTH / 2; levelText.y = 20; LK.gui.top.addChild(levelText); // Score display var scoreText = new Text2("Score: " + LK.getScore(), { size: 36, fill: 0x007BFF }); scoreText.anchor.set(1, 0); scoreText.x = VIEWPORT_WIDTH - 40; scoreText.y = 20; LK.gui.topRight.addChild(scoreText); // Initialize keyboard input handler LK.onKeyboardInput = function (text) { if (!gameState.waitingForAi) { inputText = text; inputField.updateText(inputText); } }; // Add a message to the chat function addMessage(text, isPlayer) { // Play message sound LK.getSound('message').play(); // Create new chat bubble var bubble = new ChatBubble(isPlayer, text); bubble.x = 20; // Add to message container messageContainer.addChild(bubble); gameState.bubbles.push(bubble); // Store message data gameState.messages.push({ text: text, isPlayer: isPlayer }); // Limit number of messages if (gameState.bubbles.length > MAX_MESSAGES) { var oldestBubble = gameState.bubbles.shift(); messageContainer.removeChild(oldestBubble); oldestBubble.destroy(); gameState.messages.shift(); } // Reposition all bubbles var currentY = VIEWPORT_HEIGHT - 220; for (var i = gameState.bubbles.length - 1; i >= 0; i--) { var chatBubble = gameState.bubbles[i]; currentY -= chatBubble.bubble.height + 20; chatBubble.y = currentY; } } // Send player message function sendMessage() { if (inputText.trim() === "" || gameState.waitingForAi) { return; } var messageToSend = inputText.trim(); inputText = ""; inputField.updateText(""); // Add player message to chat addMessage(messageToSend, true); // Disable input while AI is "thinking" gameState.waitingForAi = true; sendButton.disable(); // Show typing indicator typingIndicator.show(); // Process player message and generate AI response LK.setTimeout(function () { processPlayerMessage(messageToSend); typingIndicator.hide(); gameState.waitingForAi = false; sendButton.enable(); }, TYPING_DELAY); } // Process player message and generate AI response function processPlayerMessage(message) { var currentLevel = levels[gameState.currentLevel - 1]; var lowerCaseMessage = message.toLowerCase(); // Check if the player's message matches any solution for the current level var isCorrect = currentLevel.solutions.some(function (solution) { return lowerCaseMessage.includes(solution.toLowerCase()); }); // Check if player asked for a hint if (lowerCaseMessage.includes("hint") || lowerCaseMessage.includes("help")) { // Get a random hint var hintIndex = Math.floor(Math.random() * currentLevel.hints.length); addMessage(currentLevel.hints[hintIndex], false); return; } // If the answer is correct if (isCorrect) { addMessage(currentLevel.response, false); LK.getSound('correct').play(); // Update score LK.setScore(LK.getScore() + 100); scoreText.setText("Score: " + LK.getScore()); // Progress to next level LK.setTimeout(function () { if (gameState.currentLevel < levels.length) { // Add level to completed levels if not already there if (gameState.completedLevels.indexOf(gameState.currentLevel) === -1) { gameState.completedLevels.push(gameState.currentLevel); } // Move to next level gameState.currentLevel++; storage.currentLevel = gameState.currentLevel; storage.completedLevels = gameState.completedLevels; // Update level text levelText.setText("Level " + gameState.currentLevel); // Clear messages clearMessages(); // Show new level intro LK.getSound('levelUp').play(); addMessage(levels[gameState.currentLevel - 1].intro, false); } else { // Game completed gameState.completedLevels.push(gameState.currentLevel); storage.completedLevels = gameState.completedLevels; LK.showYouWin(); } }, 2000); } else { // Check for keywords to give contextual responses var foundKeyword = false; for (var i = 0; i < currentLevel.keywords.length; i++) { if (lowerCaseMessage.includes(currentLevel.keywords[i])) { foundKeyword = true; var responses = ["You're on the right track, but not quite there yet.", "That's related to the puzzle, keep thinking!", "You're getting closer to the solution.", "That's part of the puzzle, but I need the specific answer."]; var randomIndex = Math.floor(Math.random() * responses.length); addMessage(responses[randomIndex], false); break; } } // If no keywords found, give a generic response if (!foundKeyword) { var genericResponses = ["I'm not sure that's relevant to the current puzzle.", "Try focusing on the clues in the question.", "That doesn't seem to be the answer I'm looking for.", "Hmm, that's not it. Maybe approach it differently?"]; var randomIndex = Math.floor(Math.random() * genericResponses.length); addMessage(genericResponses[randomIndex], false); } } } // Clear all messages function clearMessages() { for (var i = 0; i < gameState.bubbles.length; i++) { messageContainer.removeChild(gameState.bubbles[i]); gameState.bubbles[i].destroy(); } gameState.bubbles = []; gameState.messages = []; } // Start game by showing the first level intro function startGame() { // Play background music LK.playMusic('bgMusic', { loop: true, fade: { start: 0, end: 0.2, duration: 1000 } }); // Show intro message for current level var currentLevel = levels[gameState.currentLevel - 1]; addMessage(currentLevel.intro, false); } // Initialize game startGame(); // Global event handlers game.down = function (x, y, obj) { // Handle global clicks if needed }; game.up = function (x, y, obj) { // Handle global clicks if needed }; game.move = function (x, y, obj) { // Handle global movement if needed }; // Game update loop game.update = function () { // Update score text scoreText.setText("Score: " + LK.getScore()); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
currentLevel: 1,
completedLevels: []
});
/****
* Classes
****/
var ChatBubble = Container.expand(function (isPlayer, messageText) {
var self = Container.call(this);
var bubbleShape = isPlayer ? 'inputBubble' : 'chatBubble';
var bubbleColor = isPlayer ? 0xdcf8c6 : 0xf5f5f5;
var avatarShape = isPlayer ? 'playerAvatar' : 'aiAvatar';
var bubble = self.attachAsset(bubbleShape, {
anchorX: 0,
anchorY: 0
});
bubble.alpha = 0.9;
var avatar = self.attachAsset(avatarShape, {
anchorX: 0.5,
anchorY: 0.5,
x: 40,
y: 40
});
var textMargin = 110;
var maxWidth = bubble.width - textMargin - 20;
var messageContent = new Text2(messageText, {
size: 36,
fill: 0x000000,
wordWrap: true,
wordWrapWidth: maxWidth
});
messageContent.x = textMargin;
messageContent.y = 20;
self.addChild(messageContent);
self.bubble = bubble;
self.messageContent = messageContent;
self.avatar = avatar;
self.updateText = function (newText) {
messageContent.setText(newText);
};
return self;
});
var InputField = Container.expand(function () {
var self = Container.call(this);
var background = self.attachAsset('inputBubble', {
anchorX: 0,
anchorY: 0
});
var textDisplay = new Text2("Type your message...", {
size: 36,
fill: 0x888888
});
textDisplay.x = 20;
textDisplay.y = 40;
textDisplay.anchor.set(0, 0.5);
self.addChild(textDisplay);
self.updateText = function (text) {
if (text.length === 0) {
textDisplay.setText("Type your message...");
textDisplay.setStyle({
fill: 0x888888
});
} else {
textDisplay.setText(text);
textDisplay.setStyle({
fill: 0x000000
});
}
};
self.down = function (x, y, obj) {
if (!gameState.waitingForAi) {
// Custom keyboard input handler
if (typeof LK.onKeyboardInput === 'function') {
LK.onKeyboardInput(inputText);
}
}
};
return self;
});
var SendButton = Container.expand(function () {
var self = Container.call(this);
var button = self.attachAsset('sendButton', {
anchorX: 0.5,
anchorY: 0.5
});
var buttonText = new Text2("SEND", {
size: 36,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
self.down = function (x, y, obj) {
button.alpha = 0.7;
};
self.up = function (x, y, obj) {
button.alpha = 1.0;
if (inputText.length > 0) {
sendMessage();
}
};
self.disable = function () {
button.alpha = 0.5;
self.interactive = false;
};
self.enable = function () {
button.alpha = 1.0;
self.interactive = true;
};
return self;
});
var TypingIndicator = Container.expand(function () {
var self = Container.call(this);
var dots = [];
var dotSpacing = 24;
for (var i = 0; i < 3; i++) {
var dot = self.attachAsset('typingIndicator', {
anchorX: 0.5,
anchorY: 0.5,
x: i * dotSpacing,
y: 0
});
dots.push(dot);
}
self.visible = false;
self.animate = function () {
dots.forEach(function (dot, i) {
var delay = i * 200;
var startY = 0;
// Stop any existing animations
tween.stop(dot);
// Reset position
dot.y = startY;
// Create bobbing animation
var _animateDot = function animateDot() {
tween(dot, {
y: startY - 8
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(dot, {
y: startY
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: _animateDot
});
}
});
};
// Start animation with delay based on dot index
LK.setTimeout(_animateDot, delay);
});
};
self.show = function () {
self.visible = true;
self.animate();
};
self.hide = function () {
self.visible = false;
dots.forEach(function (dot) {
tween.stop(dot);
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xECE5DD
});
/****
* Game Code
****/
// Game constants
var VIEWPORT_WIDTH = 2048;
var VIEWPORT_HEIGHT = 2732;
var MAX_MESSAGES = 8;
var TYPING_DELAY = 2000;
// Game state
var gameState = {
currentLevel: storage.currentLevel || 1,
completedLevels: storage.completedLevels || [],
messages: [],
bubbles: [],
inputText: "",
waitingForAi: false,
typingTimer: null
};
// Levels data
var levels = [{
id: 1,
intro: "Welcome to ChatQuest! I'm your AI assistant. To complete this level, tell me the secret password: 'puzzle'.",
hints: ["Try asking for the password", "The password is hidden in the instructions"],
solutions: ["puzzle", "the password is puzzle"],
response: "Correct! The password 'puzzle' unlocks the first level. You're ready to proceed to the next challenge!",
keywords: ["password", "puzzle"]
}, {
id: 2,
intro: "Level 2: I'm thinking of a number between 1 and 10. If you multiply it by 3 and add 2, you get 17. What number am I thinking of?",
hints: ["Try to solve the equation", "If 3x + 2 = 17, then what is x?"],
solutions: ["5", "the number is 5", "five"],
response: "Well done! 5 is correct because 5 × 3 + 2 = 17. Your logical thinking is impressive!",
keywords: ["number", "equation", "multiply"]
}, {
id: 3,
intro: "Level 3: To proceed, tell me which word doesn't belong in this list: Apple, Mars, Earth, Jupiter, Neptune.",
hints: ["Think about categories", "What do most items have in common?"],
solutions: ["apple", "the odd one is apple"],
response: "Excellent! Apple is the only fruit while the others are planets. Your pattern recognition skills are strong!",
keywords: ["apple", "planet", "fruit", "belong"]
}, {
id: 4,
intro: "Final Level: I have created a riddle just for you. I'm always hungry, I must always be fed. The finger I touch, will soon turn red. What am I?",
hints: ["Think about things that consume", "What might make your finger turn red if you touch it?"],
solutions: ["fire", "a fire", "it's fire"],
response: "Congratulations! Fire is correct. You've successfully completed ChatQuest! You're a true puzzle master!",
keywords: ["hungry", "finger", "red", "fire", "burn"]
}];
// Input text
var inputText = "";
// Initialize UI elements
var messageContainer = new Container();
game.addChild(messageContainer);
var inputField = new InputField();
inputField.x = 20;
inputField.y = VIEWPORT_HEIGHT - 140;
game.addChild(inputField);
var sendButton = new SendButton();
sendButton.x = VIEWPORT_WIDTH - 80;
sendButton.y = VIEWPORT_HEIGHT - 80;
game.addChild(sendButton);
var typingIndicator = new TypingIndicator();
typingIndicator.x = 130;
typingIndicator.y = VIEWPORT_HEIGHT - 200;
game.addChild(typingIndicator);
// Level display
var levelText = new Text2("Level " + gameState.currentLevel, {
size: 40,
fill: 0x007BFF
});
levelText.anchor.set(0.5, 0);
levelText.x = VIEWPORT_WIDTH / 2;
levelText.y = 20;
LK.gui.top.addChild(levelText);
// Score display
var scoreText = new Text2("Score: " + LK.getScore(), {
size: 36,
fill: 0x007BFF
});
scoreText.anchor.set(1, 0);
scoreText.x = VIEWPORT_WIDTH - 40;
scoreText.y = 20;
LK.gui.topRight.addChild(scoreText);
// Initialize keyboard input handler
LK.onKeyboardInput = function (text) {
if (!gameState.waitingForAi) {
inputText = text;
inputField.updateText(inputText);
}
};
// Add a message to the chat
function addMessage(text, isPlayer) {
// Play message sound
LK.getSound('message').play();
// Create new chat bubble
var bubble = new ChatBubble(isPlayer, text);
bubble.x = 20;
// Add to message container
messageContainer.addChild(bubble);
gameState.bubbles.push(bubble);
// Store message data
gameState.messages.push({
text: text,
isPlayer: isPlayer
});
// Limit number of messages
if (gameState.bubbles.length > MAX_MESSAGES) {
var oldestBubble = gameState.bubbles.shift();
messageContainer.removeChild(oldestBubble);
oldestBubble.destroy();
gameState.messages.shift();
}
// Reposition all bubbles
var currentY = VIEWPORT_HEIGHT - 220;
for (var i = gameState.bubbles.length - 1; i >= 0; i--) {
var chatBubble = gameState.bubbles[i];
currentY -= chatBubble.bubble.height + 20;
chatBubble.y = currentY;
}
}
// Send player message
function sendMessage() {
if (inputText.trim() === "" || gameState.waitingForAi) {
return;
}
var messageToSend = inputText.trim();
inputText = "";
inputField.updateText("");
// Add player message to chat
addMessage(messageToSend, true);
// Disable input while AI is "thinking"
gameState.waitingForAi = true;
sendButton.disable();
// Show typing indicator
typingIndicator.show();
// Process player message and generate AI response
LK.setTimeout(function () {
processPlayerMessage(messageToSend);
typingIndicator.hide();
gameState.waitingForAi = false;
sendButton.enable();
}, TYPING_DELAY);
}
// Process player message and generate AI response
function processPlayerMessage(message) {
var currentLevel = levels[gameState.currentLevel - 1];
var lowerCaseMessage = message.toLowerCase();
// Check if the player's message matches any solution for the current level
var isCorrect = currentLevel.solutions.some(function (solution) {
return lowerCaseMessage.includes(solution.toLowerCase());
});
// Check if player asked for a hint
if (lowerCaseMessage.includes("hint") || lowerCaseMessage.includes("help")) {
// Get a random hint
var hintIndex = Math.floor(Math.random() * currentLevel.hints.length);
addMessage(currentLevel.hints[hintIndex], false);
return;
}
// If the answer is correct
if (isCorrect) {
addMessage(currentLevel.response, false);
LK.getSound('correct').play();
// Update score
LK.setScore(LK.getScore() + 100);
scoreText.setText("Score: " + LK.getScore());
// Progress to next level
LK.setTimeout(function () {
if (gameState.currentLevel < levels.length) {
// Add level to completed levels if not already there
if (gameState.completedLevels.indexOf(gameState.currentLevel) === -1) {
gameState.completedLevels.push(gameState.currentLevel);
}
// Move to next level
gameState.currentLevel++;
storage.currentLevel = gameState.currentLevel;
storage.completedLevels = gameState.completedLevels;
// Update level text
levelText.setText("Level " + gameState.currentLevel);
// Clear messages
clearMessages();
// Show new level intro
LK.getSound('levelUp').play();
addMessage(levels[gameState.currentLevel - 1].intro, false);
} else {
// Game completed
gameState.completedLevels.push(gameState.currentLevel);
storage.completedLevels = gameState.completedLevels;
LK.showYouWin();
}
}, 2000);
} else {
// Check for keywords to give contextual responses
var foundKeyword = false;
for (var i = 0; i < currentLevel.keywords.length; i++) {
if (lowerCaseMessage.includes(currentLevel.keywords[i])) {
foundKeyword = true;
var responses = ["You're on the right track, but not quite there yet.", "That's related to the puzzle, keep thinking!", "You're getting closer to the solution.", "That's part of the puzzle, but I need the specific answer."];
var randomIndex = Math.floor(Math.random() * responses.length);
addMessage(responses[randomIndex], false);
break;
}
}
// If no keywords found, give a generic response
if (!foundKeyword) {
var genericResponses = ["I'm not sure that's relevant to the current puzzle.", "Try focusing on the clues in the question.", "That doesn't seem to be the answer I'm looking for.", "Hmm, that's not it. Maybe approach it differently?"];
var randomIndex = Math.floor(Math.random() * genericResponses.length);
addMessage(genericResponses[randomIndex], false);
}
}
}
// Clear all messages
function clearMessages() {
for (var i = 0; i < gameState.bubbles.length; i++) {
messageContainer.removeChild(gameState.bubbles[i]);
gameState.bubbles[i].destroy();
}
gameState.bubbles = [];
gameState.messages = [];
}
// Start game by showing the first level intro
function startGame() {
// Play background music
LK.playMusic('bgMusic', {
loop: true,
fade: {
start: 0,
end: 0.2,
duration: 1000
}
});
// Show intro message for current level
var currentLevel = levels[gameState.currentLevel - 1];
addMessage(currentLevel.intro, false);
}
// Initialize game
startGame();
// Global event handlers
game.down = function (x, y, obj) {
// Handle global clicks if needed
};
game.up = function (x, y, obj) {
// Handle global clicks if needed
};
game.move = function (x, y, obj) {
// Handle global movement if needed
};
// Game update loop
game.update = function () {
// Update score text
scoreText.setText("Score: " + LK.getScore());
};