changePage('gameIntroduction'));
gameIntroBtn.addEventListener('click', () => changePage('gameplayInstructions'));
gameplayInstructionsBtn.addEventListener('click', function() {
if (!isLoadingStage) {
isLoadingStage = true;
// show loading spinner
document.querySelector('.smooth.spinner').classList.remove('d-none');
document.getElementById('overlay').classList.remove('d-none');
setTimeout(() => {
numberOfTargets = 3; // update the HTMLView Element to hold new number of "child" elements
initialiseNewStage(numberOfTargets);
_play();
}, 2000);
// the function MUST be called some time after initialising the cluster of nodes (targets) to
// prevent error of not being able to find the new targets in the DOM after initialising them.
// therefore, we call the function below 50ms later.
// if we call it much later, the user can see the color and type of clusters change mid way
// while playing and we don't want that.
setTimeout(() => {
randomiseTargetClusterImages();
currentPage = "gameScreen";
// display instructions
showInstructionsToHitNumberOfTargets(3);
_view.backgroundMusic.play();
isLoadingStage = false;
document.querySelector('.smooth.spinner').classList.add('d-none');
document.getElementById('overlay').classList.add('d-none');
setTimeout(() => {
hideInstructionsToHitNumberOfTargets();
}, 10000);
}, 2050);
}
// due to difficulties in implementing a double click event handler for firing bullets
// I used a single click event handler to simulate a double click event.
function handleSingleClick(event) {
const currentTime = new Date().getTime();
const timeElapsed = currentTime - lastClickTime;
if (timeElapsed <= doubleClickDelay) {
clearTimeout(clickTimeout);
handleDoubleClick(event);
} else {
clickTimeout = setTimeout(() => {
handleSingleClickAction(event);
}, doubleClickDelay);
}
lastClickTime = currentTime;
}
function handleSingleClickAction(event) {
// Perform the action for a single click
}
// Define the double-click action function
function handleDoubleClick(event) {
event.stopPropagation();
if (canFireBullet) {
// limited number of laser nodes given each stage.
_println("===attempt to fire laser node from robot hand===");
const availableBulletIndex = y.findIndex(x => x == 2);
allBulletsAreExhausted = y.every(x => x > 2);
// only allow bullet to be fired if condition below is true.
if (!allBulletsAreExhausted) {
_println('are bullets exhausted? '+ allBulletsAreExhausted);
_println('=====bullet fires!=====');
_println("bullet index: " + availableBulletIndex + " is fired.");
if (!isMuted) _view.fireLaserNodeSound.play();
// shoot available bullet!
x[availableBulletIndex] = xc;
vy[availableBulletIndex] = 18;
shouldShowBullets[availableBulletIndex] = true;
}
// updating remaining laser nodes
let currentShownNumberOfLaserNodes = document.getElementById('number-of-remaining-nodes').innerText;
if (parseInt(currentShownNumberOfLaserNodes) > 0) {
document.getElementById('number-of-remaining-nodes').innerText = `${parseInt(currentShownNumberOfLaserNodes) - 1}`;
}
}
}
// Get the target element and add the click event listener
const targetElement1 = document.getElementById('gameScreen');
targetElement1.addEventListener('click', handleSingleClick);
const targetElement2 = document.getElementById('objectImageDesktop');
targetElement2.addEventListener('click', handleSingleClick);
});
]]>
{
let isDragging = false;
const draggable = document.getElementById('objectImageDesktop');
console.log(draggable);
draggable.addEventListener('mousedown', (e) => {
isDragging = true;
console.log('mousedown');
let startX = e.clientX;
let startY = e.clientY;
const moveHandler = (e) => {
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
draggable.style.left = `${draggable.offsetLeft + deltaX}px`;
draggable.style.top = `${draggable.offsetTop + deltaY}px`;
startX = e.clientX;
startY = e.clientY;
};
const upHandler = () => {
isDragging = false;
// Remove event listeners when dragging is done
document.removeEventListener('mousemove', moveHandler);
document.removeEventListener('mouseup', upHandler);
};
// Attach event listeners for mousemove and mouseup
document.addEventListener('mousemove', moveHandler);
document.addEventListener('mouseup', upHandler);
});
// Double click event
draggable.addEventListener('dblclick', (e) => {
if (!isDragging) {
// Handle your double click logic here
alert('Double click!');
}
});
}, 2000);
]]>
{
isDragging = true;
startPosX = e.clientX;
startPosY = e.clientY;
offsetX = draggableElement.offsetLeft;
offsetY = draggableElement.offsetTop;
draggableElement.classList.add("dragging");
});
document.addEventListener("mousemove", (e) => {
if (!isDragging) return;
const deltaX = e.clientX - startPosX;
const newPosX = offsetX + deltaX;
draggableElement.style.left = `${newPosX}px`;
});
document.addEventListener("mouseup", () => {
if (!isDragging) return;
isDragging = false;
draggableElement.classList.remove("dragging");
});
function getDraggableElementXPosition() {
const draggableElement = document.getElementById("draggable");
const draggableElementRect = draggableElement.getBoundingClientRect();
return draggableElementRect.left + (draggableElementRect.width / 2);
}
draggableElement.addEventListener("dblclick", () => {
// Call your double click handler function here
hideInstructionsToHitNumberOfTargets();
// limited number of laser nodes given each stage.
_println("===attempt to fire laser node from robot hand===");
const availableBulletIndex = y.findIndex(x => x == 2);
allBulletsAreExhausted = y.every(x => x > 2);
// only allow bullet to be fired if condition below is true.
if (!allBulletsAreExhausted) {
_println('are bullets exhausted? '+ allBulletsAreExhausted);
_println('=====bullet fires!=====');
_println("bullet index: " + availableBulletIndex + " is fired.");
if (!isMuted) _view.fireLaserNodeSound.play();
// shoot available bullet! (note: bullet position is in pixel units)
x[availableBulletIndex] = getDraggableElementXPosition();
vy[availableBulletIndex] = 600;
shouldShowBullets[availableBulletIndex] = true;
}
// updating remaining laser nodes
let currentShownNumberOfLaserNodes = document.getElementById('number-of-remaining-nodes').innerText;
if (parseInt(currentShownNumberOfLaserNodes) > 0) {
document.getElementById('number-of-remaining-nodes').innerText = `${parseInt(currentShownNumberOfLaserNodes) - 1}`;
}
});
]]>
{
slideContainer.style.bottom = '-100%';
});
const userAgent = navigator.userAgent;
console.log('User-Agent String:', userAgent);
// Check if the User-Agent string contains specific keywords
if (userAgent.match(/Android/i)) {
console.log('This user is using an Android device.');
slideContainer.style.display = 'block';
slideContainer.style.bottom = '0';
} else if (userAgent.match(/iPhone|iPad|iPod/i)) {
console.log('This user is using an iOS device (iPhone, iPad, or iPod).');
slideContainer.style.display = 'block';
slideContainer.style.bottom = '0';
} else if (userAgent.match(/Windows/i)) {
console.log('This user is using a Windows device.');
} else if (userAgent.match(/Macintosh/i)) {
console.log('This user is using a Macintosh (Mac) device.');
} else {
console.log('The user agent does not match any known device/OS.');
}
]]>
0;
}
startQuestion(questionName) {
/*
* This function should be called whenever the student first sees the question,
* and the student cannot start answering any other question. The question may be subsequently
* ended by calling endQuestion function.
*
* Parameters:
* questionName: string
*
* return value:
* undefined
*/
_view._addInteraction(this._nullFunction,
{
action:"questionStart",
name:questionName
},
{
element:"questionLib",
property:"value"
}
);
this._debugPrint("Start question: " + questionName);
this._questionLib.stack.push(questionName);
}
endQuestion() {
/*
* This function should be called whenever the student submits/finalises their answer for the question.
*
* No Parameters
*
* return value:
* undefined
*/
if (this._questionLib.stack.length > 0) {
const questionName = this._questionLib.stack.pop();
this._debugPrint("End question: " + questionName);
_view._addInteraction(this._nullFunction,
{
action:"questionEnd",
name:questionName
},
{
element: "questionLib",
property: "value"
}
);
}
}
// for assessment.json history
addQuestionHistory(history, questionName=null) {
/*
* Adds an entry to the question history to the specified question, or to the pending question by default.
* If adding to the question history of the pending question, it also flushes the history. (which makes Moodle able to read the history)
*
* Parameters:
* history: string, questionName?: string
*
* return value:
* undefined
*
* questioName:
* This is the name of the question that the history will be added to. If this parameter is not set, by default, the history entry
* will be added to the pending question.
*/
if (questionName === null && this.isQuestionStarted()) {
questionName = this._getCurrentQuestion();
}
if (!(questionName in this._questionLib.history)) {
this._debugPrint("Create question history for " + questionName);
this._questionLib.history[questionName] = [];
}
if (this.debugMode) {
console.log("Push \"" + history + "\" to question history for " + questionName);
}
this._questionLib.history[questionName].push(history);
this._flushQuestionHistory(questionName);
}
_flushQuestionHistory(questionName) {
if (questionName === this._getCurrentQuestion()) {
const outputHistory = this._getQuestionHistory(questionName);
_view._addInteraction(this._nullFunction,
outputHistory,
{
property: "historyFor" + questionName,
element: "questionLib"
}
);
}
}
_getQuestionHistory(questionName) {
if (questionName in this._questionLib.history) {
return this._questionLib.history[questionName].join("\n");
} else {
_debugPrint("No question \"" + questionName + "\" exists");
return "";
}
}
onAnswer(answer, isCorrect=false, history=answer, questionName=null) {
/*
* Indicates that the student made an attempt to answer the question,
* writes the history, and adds a log for the submitted answer.
* However, it does not give marks in itself (that is done by awardQuestionMarks)
*
* Parameters:
* answer: String
* A string representing what the student chose as the answer to the question.
* This can be used in an advanced assessment.json generator to check the answers on the server side.
*
* isCorrect?: Boolean
* Whether the student's answer is correct (defaults to false)
*
* history: String
* A textual representation of what the student chose as the answer to the question.
* By default, this is the same as the value given in the parameter answer.
* This parameter controls what the teacher will see when they hover their cursor over the student's marks for this question.
*
* questionName?: String
* The name of the question the student is giving an answer to.
* By default, this is the pending question.
* However, this can be overridden using this parameter to write the answer history to some other question.
*/
if (questionName === null && this.isQuestionStarted()) {
questionName = this._questionLib.stack[this._questionLib.stack.length - 1];
}
if (questionName !== null) {
const explainer = Object.create(null);
explainer[true] = " ✅";
explainer[false] = " ❌";
this.addQuestionHistory(history + explainer[isCorrect], questionName);
if (questionName === this._getCurrentQuestion()) {
_view._addInteraction(this._nullFunction, {name:questionName, answer:answer, isCorrect:isCorrect, action:"questionAnswer"}, {property: "answer", element:"questionLib"});
}
}
}
awardQuestionMarks(marks=1) {
/*
* Awards a number of marks to the student for the pending question.
*
* Parameters:
* marks?: int
*
* The number of marks to award to the student. By default, this value is 1.
*/
if (this.isQuestionStarted()) {
const questionName = this._getCurrentQuestion();
this._questionLib.questionMarksAwarded[questionName] = marks;
_view._addInteraction(
this._nullFunction,
this._questionLib.questionMarksAwarded[questionName],
{
element: "questionLib",
property: "awardMarkFor"+questionName
}
);
}
}
resetQuestionMarks(questionName) {
/*
* Reset the awarded marks for the indicated question to 0 the next time the question is started.
*
* Parameters:
* questionName: string
*
* return value:
* undefined
*
* questionName:
* The name of the question to reset.
*/
this._questionLib.questionMarksAwarded[questionName] = 0;
}
questionInstantMark(questionName, message) {
/*
* A convenience function to start a question, award a mark, (optionally) add a history entry, and end the question.
* This function should be called whenever a student answers a question correctly.
* It instantly submits the correct answer to the Moodle server, and gives the student a mark.
* For most cases, this function would suffice.
*
* Parameters:
* questionName: string, message?: string
*
* return value:
* undefined
*
* questionName:
* These values are the names of the questions that will be displayed on the analytics page on Moodle,
* and they'll be needed when you make the assessment.json file so please remember to note down what
* values you use for the name of each question.
*
* message:
* What Moodle will display when the teacher hovers their mouse over the student's mark for a particular question.
*/
this.startQuestion(questionName);
this._debugPrint("" + message);
if (message) {
this.addQuestionHistory(message);
} else {
this._flushQuestionHistory(questionName);
}
this.awardQuestionMarks();
this.endQuestion();
}
questionAppendHistory(questionName, message) {
/*
* Adds a history entry to the given question.
*
* Parameters:
* questionName: string, message?: string
*
* return value:
* undefined
*
* questionName:
* The name of the question to append the history entry to.
*
* message:
* The history entry to add to the question.
*/
if (!(questionName in this._questionLib.questionMarksAwarded)) {
this._questionLib.questionMarksAwarded[questionName] = 0;
}
let shouldPushQuestion = this._getCurrentQuestion() !== questionName;
if (shouldPushQuestion) {
this.startQuestion(questionName);
}
this.awardQuestionMarks(this._questionLib.questionMarksAwarded[questionName])
this.addQuestionHistory(message);
if (shouldPushQuestion) {
this.endQuestion();
}
}
resetQuestionHistory(questionName) {
/*
* Clear/reset the history of the indicated question.
*
* Parameters:
* questionName: string
*
* return value:
* undefined
*
* questionName:
* The name of the question to reset.
*/
this._questionLib.history[questionName] = [];
}
resetQuestion(questionName) {
/*
* A convenience function to reset the marks and history of an indicated question.
*/
this.resetQuestionHistory(questionName);
this.resetQuestionMarks(questionName);
}
}
let moodle = new MoodleDataAnalyticsLibrary();
]]>
= 1 && level < 100) {
progressLevel.classList.remove("emptybar");
progressLevel.classList.remove("fullbar");
} else {
progressLevel.classList.remove("emptybar");
progressLevel.classList.add("fullbar");
}
}
]]>
to fire!";
instructions.appendChild(howToFireBulletInstruction);
shouldShowAnimatedClick = true;
}
instructions.classList.add('d-visible');
instructions.style.margin = '0';
}
function hideInstructionsToHitNumberOfTargets() {
let instructions = document.getElementById('stageInstructions');
instructions.classList.remove('d-visible');
shouldShowAnimatedClick = false;
}
]]>
{
// console.log(voice.name, voice.lang)
//})
//debug
// Queue this utterance.
window.speechSynthesis.speak(msg);
}
]]>
For an enhanced experience, using a laptop is recommended.
]]>