/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
level: 1,
currentDocIndex: 0
});
/****
* Classes
****/
var DiagramZone = Container.expand(function () {
var self = Container.call(this);
self.zoneText = new Text2('zone', {
size: 24,
fill: '#333333'
});
self.addChild(self.zoneText);
self.interactive = true;
self.linkedFrom = null;
self.zoneId = '';
self.correctWord = '';
self.setup = function (label, posX, posY, zoneId, correctWord) {
self.zoneText.setText(label);
self.x = posX;
self.y = posY;
self.zoneId = zoneId;
self.correctWord = correctWord;
};
self.highlight = function () {
tween(self, {
alpha: 0.6
}, {
duration: 150
});
};
self.unhighlight = function () {
tween(self, {
alpha: 1
}, {
duration: 150
});
};
return self;
});
var DocumentPair = Container.expand(function () {
var self = Container.call(this);
self.scenario = '';
self.umlDiagram = null;
self.errors = [];
self.selectableWords = [];
self.links = [];
self.isApproved = false;
self.isRejected = false;
self.setup = function (scenario, classData, errors) {
self.scenario = scenario;
self.errors = errors;
var textBox = LK.getAsset('textBox', {
anchorX: 0,
anchorY: 0
});
textBox.x = 50;
textBox.y = 50;
self.addChild(textBox);
var scenarioText = new Text2(scenario, {
size: 24,
fill: '#333333'
});
scenarioText.anchor.set(0, 0);
scenarioText.x = 70;
scenarioText.y = 70;
scenarioText.wordWrap = true;
scenarioText.wordWrapWidth = 850;
self.addChild(scenarioText);
var diagramBox = LK.getAsset('diagramBox', {
anchorX: 0,
anchorY: 0
});
diagramBox.x = 50;
diagramBox.y = 480;
self.addChild(diagramBox);
self.umlDiagram = new UMLClassDiagram();
self.umlDiagram.x = 250;
self.umlDiagram.y = 600;
self.umlDiagram.addClassBox(classData.name, classData.properties, classData.methods);
self.addChild(self.umlDiagram);
};
self.approve = function () {
self.isApproved = true;
LK.getSound('stamp').play();
var hasErrors = self.errors.length > 0 && !self.hasFoundAllErrors();
return !hasErrors;
};
self.reject = function () {
self.isRejected = true;
LK.getSound('stamp').play();
var hasErrors = self.errors.length > 0;
return hasErrors;
};
self.hasFoundAllErrors = function () {
return self.links.length >= self.errors.length;
};
return self;
});
var SelectableWord = Container.expand(function () {
var self = Container.call(this);
self.wordText = new Text2('word', {
size: 32,
fill: '#000000'
});
self.addChild(self.wordText);
self.interactive = true;
self.isSelected = false;
self.linkedTo = null;
self.setup = function (word, posX, posY) {
self.wordText.setText(word);
self.x = posX;
self.y = posY;
self.word = word;
};
self.select = function () {
self.isSelected = true;
var highlight = LK.getAsset('selectedWord', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChildAt(highlight, 0);
tween(self, {
alpha: 0.8
}, {
duration: 200
});
};
self.deselect = function () {
self.isSelected = false;
if (self.children.length > 1) {
self.removeChildAt(0);
}
tween(self, {
alpha: 1
}, {
duration: 200
});
};
return self;
});
var UMLClassDiagram = Container.expand(function () {
var self = Container.call(this);
self.className = '';
self.properties = [];
self.methods = [];
self.zones = [];
self.addClassBox = function (classNameStr, properties, methods) {
self.className = classNameStr;
self.properties = properties;
self.methods = methods;
var classGraphics = LK.getAsset('classBox', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(classGraphics);
var titleText = new Text2(classNameStr, {
size: 28,
fill: '#000000'
});
titleText.anchor.set(0.5, 0);
titleText.y = -80;
self.addChild(titleText);
var yOffset = -50;
for (var i = 0; i < properties.length; i++) {
var propText = new Text2('- ' + properties[i].name + ': ' + properties[i].type, {
size: 20,
fill: '#333333'
});
propText.anchor.set(0.5, 0);
propText.y = yOffset;
self.addChild(propText);
var zone = new DiagramZone();
zone.setup(properties[i].name, 0, yOffset, 'prop_' + i, properties[i].name);
self.zones.push(zone);
yOffset += 25;
}
yOffset += 10;
for (var j = 0; j < methods.length; j++) {
var methodText = new Text2('+ ' + methods[j].name + '()', {
size: 20,
fill: '#333333'
});
methodText.anchor.set(0.5, 0);
methodText.y = yOffset;
self.addChild(methodText);
var mzone = new DiagramZone();
mzone.setup(methods[j].name, 0, yOffset, 'method_' + j, methods[j].name);
self.zones.push(mzone);
yOffset += 25;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xffffff
});
/****
* Game Code
****/
/****
* Game Data
****/
var levelData = [{
name: 'Class Basics',
documents: [{
scenario: 'A bank account has a balance and an owner name. It can deposit money and withdraw money.',
classData: {
name: 'BankAccount',
properties: [{
name: 'balance',
type: 'float'
}, {
name: 'owner',
type: 'string'
}],
methods: [{
name: 'deposit',
type: 'void'
}, {
name: 'withdraw',
type: 'void'
}]
},
errors: []
}, {
scenario: 'A car has color, model, and year. It can start engine and stop engine.',
classData: {
name: 'Car',
properties: [{
name: 'color',
type: 'string'
}, {
name: 'model',
type: 'string'
}],
methods: [{
name: 'startEngine',
type: 'void'
}, {
name: 'stopEngine',
type: 'void'
}]
},
errors: [{
type: 'missingProperty',
expected: 'year'
}]
}, {
scenario: 'A student has name, ID, and grade. They can submit assignment and get grades.',
classData: {
name: 'Student',
properties: [{
name: 'name',
type: 'string'
}, {
name: 'studentId',
type: 'int'
}],
methods: [{
name: 'submitAssignment',
type: 'void'
}, {
name: 'getGrades',
type: 'array'
}]
},
errors: [{
type: 'missingProperty',
expected: 'grade'
}]
}, {
scenario: 'A book has title, author, and ISBN. It can be borrowed and returned.',
classData: {
name: 'Book',
properties: [{
name: 'title',
type: 'string'
}, {
name: 'author',
type: 'string'
}, {
name: 'isbn',
type: 'string'
}],
methods: [{
name: 'borrow',
type: 'void'
}, {
name: 'return',
type: 'void'
}]
},
errors: []
}, {
scenario: 'A person has first name, last name, and age. They can walk and eat.',
classData: {
name: 'Person',
properties: [{
name: 'firstName',
type: 'string'
}, {
name: 'lastName',
type: 'string'
}],
methods: [{
name: 'walk',
type: 'void'
}, {
name: 'eat',
type: 'void'
}]
},
errors: [{
type: 'missingProperty',
expected: 'age'
}]
}]
}, {
name: 'Access Modifiers',
documents: [{
scenario: 'A password manager stores encrypted passwords (private). Users can set and verify passwords (public).',
classData: {
name: 'PasswordManager',
properties: [{
name: '- encryptedPassword',
type: 'string'
}, {
name: '+ userId',
type: 'int'
}],
methods: [{
name: '+ setPassword',
type: 'void'
}, {
name: '+ verifyPassword',
type: 'boolean'
}]
},
errors: []
}, {
scenario: 'A database connection should have private host and port. Public methods connect and disconnect.',
classData: {
name: 'DatabaseConnection',
properties: [{
name: '- host',
type: 'string'
}, {
name: '+ port',
type: 'int'
}],
methods: [{
name: '+ connect',
type: 'void'
}, {
name: '+ disconnect',
type: 'void'
}]
},
errors: [{
type: 'wrongAccessModifier',
expected: 'port should be private'
}]
}, {
scenario: 'A calculator performs private validation on inputs. Public methods add, subtract, and multiply.',
classData: {
name: 'Calculator',
properties: [],
methods: [{
name: '+ add',
type: 'double'
}, {
name: '+ subtract',
type: 'double'
}, {
name: '+ multiply',
type: 'double'
}]
},
errors: [{
type: 'missingMethod',
expected: 'validate method should be private'
}]
}, {
scenario: 'A bank account has private balance and private PIN. Public methods deposit and withdraw.',
classData: {
name: 'SecureAccount',
properties: [{
name: '- balance',
type: 'float'
}, {
name: '- pin',
type: 'string'
}],
methods: [{
name: '+ deposit',
type: 'void'
}, {
name: '+ withdraw',
type: 'void'
}]
},
errors: []
}, {
scenario: 'A user profile has public name and email. Password is private. Can update profile publicly.',
classData: {
name: 'UserProfile',
properties: [{
name: '+ name',
type: 'string'
}, {
name: '+ email',
type: 'string'
}, {
name: '- password',
type: 'string'
}],
methods: [{
name: '+ updateProfile',
type: 'void'
}]
},
errors: []
}]
}, {
name: 'Inheritance & Multiplicity',
documents: [{
scenario: 'A company has many employees. Each employee has a name and salary. Employees can work.',
classData: {
name: 'Company',
properties: [{
name: 'name',
type: 'string'
}, {
name: 'employees',
type: 'list<Employee>'
}],
methods: [{
name: 'addEmployee',
type: 'void'
}, {
name: 'getEmployees',
type: 'list<Employee>'
}]
},
errors: []
}, {
scenario: 'A vehicle is a parent class. Cars and motorcycles are vehicles. Vehicles have speed and can stop.',
classData: {
name: 'Vehicle',
properties: [{
name: 'speed',
type: 'double'
}],
methods: [{
name: 'accelerate',
type: 'void'
}, {
name: 'stop',
type: 'void'
}]
},
errors: []
}, {
scenario: 'A library has many books. A book belongs to one library. Books have ISBN and title.',
classData: {
name: 'Library',
properties: [{
name: 'name',
type: 'string'
}, {
name: 'books',
type: 'list<Book>'
}],
methods: [{
name: 'addBook',
type: 'void'
}]
},
errors: [{
type: 'missingProperty',
expected: 'location'
}]
}, {
scenario: 'An animal eats and sleeps. Dogs and cats are animals that can bark and meow.',
classData: {
name: 'Animal',
properties: [{
name: 'name',
type: 'string'
}],
methods: [{
name: 'eat',
type: 'void'
}, {
name: 'sleep',
type: 'void'
}]
},
errors: []
}, {
scenario: 'A school has many students and many teachers. Students study. Teachers teach.',
classData: {
name: 'School',
properties: [{
name: 'name',
type: 'string'
}, {
name: 'students',
type: 'list<Student>'
}, {
name: 'teachers',
type: 'list<Teacher>'
}],
methods: [{
name: 'addStudent',
type: 'void'
}, {
name: 'addTeacher',
type: 'void'
}]
},
errors: []
}]
}];
/****
* Game Variables
****/
var currentLevel = storage.level - 1;
var currentDocIndex = storage.currentDocIndex;
var currentDocPair = null;
var selectedWord = null;
var score = LK.getScore();
var documents = [];
var approveButton = null;
var rejectButton = null;
var rulePanelVisible = false;
var rulePanel = null;
/****
* Helper Functions
****/
function initializeLevel() {
var level = levelData[currentLevel];
documents = [];
for (var i = 0; i < level.documents.length; i++) {
var doc = new DocumentPair();
doc.setup(level.documents[i].scenario, level.documents[i].classData, level.documents[i].errors);
documents.push(doc);
}
loadDocument(0);
}
function loadDocument(docIndex) {
if (docIndex >= documents.length) {
showDayComplete();
return;
}
currentDocIndex = docIndex;
currentDocPair = documents[docIndex];
game.removeChildren();
game.addChild(currentDocPair);
createControlUI();
createRulePanel();
}
function createControlUI() {
var approveGraphics = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
});
approveButton = new Container();
approveButton.addChild(approveGraphics);
approveButton.interactive = true;
var approveText = new Text2('APPROVE', {
size: 32,
fill: '#ffffff'
});
approveText.anchor.set(0.5, 0.5);
approveButton.addChild(approveText);
approveButton.x = 1850;
approveButton.y = 1400;
game.addChild(approveButton);
var rejectGraphics = LK.getAsset('rejectButton', {
anchorX: 0.5,
anchorY: 0.5
});
rejectButton = new Container();
rejectButton.addChild(rejectGraphics);
rejectButton.interactive = true;
var rejectText = new Text2('REJECT', {
size: 32,
fill: '#ffffff'
});
rejectText.anchor.set(0.5, 0.5);
rejectButton.addChild(rejectText);
rejectButton.x = 1850;
rejectButton.y = 1550;
game.addChild(rejectButton);
var scoreText = new Text2('Score: ' + score, {
size: 40,
fill: '#000000'
});
scoreText.anchor.set(0, 0);
scoreText.x = 50;
scoreText.y = 1900;
game.addChild(scoreText);
scoreText.scoreDisplay = true;
var docCountText = new Text2('Document ' + (currentDocIndex + 1) + ' / ' + documents.length, {
size: 32,
fill: '#666666'
});
docCountText.anchor.set(0, 0);
docCountText.x = 50;
docCountText.y = 1960;
game.addChild(docCountText);
}
function createRulePanel() {
var level = levelData[currentLevel];
var ruleGraphics = LK.getAsset('rulePanel', {
anchorX: 0,
anchorY: 0
});
rulePanel = new Container();
rulePanel.addChild(ruleGraphics);
rulePanel.x = 1050;
rulePanel.y = 50;
var ruleTitle = new Text2('RULES', {
size: 28,
fill: '#333333'
});
ruleTitle.anchor.set(0.5, 0);
ruleTitle.x = 150;
ruleTitle.y = 20;
rulePanel.addChild(ruleTitle);
var ruleText = '';
if (currentLevel === 0) {
ruleText = 'L1: Basic Classes\n\nVerify properties match the description\n\nVerify methods match the description';
} else if (currentLevel === 1) {
ruleText = 'L2: Access Modifiers\n\n+ = public\n- = private\n\nData should be private\nMethods should be public';
} else {
ruleText = 'L3: Inheritance\n\nParent classes define shared behavior\n\nMultiplicity: one-to-many relationships';
}
var ruleContentText = new Text2(ruleText, {
size: 18,
fill: '#555555'
});
ruleContentText.anchor.set(0, 0);
ruleContentText.x = 20;
ruleContentText.y = 70;
ruleContentText.wordWrap = true;
ruleContentText.wordWrapWidth = 260;
rulePanel.addChild(ruleContentText);
game.addChild(rulePanel);
}
function showDayComplete() {
game.removeChildren();
var bgBox = LK.getAsset('diagramBox', {
anchorX: 0.5,
anchorY: 0.5
});
var bgContainer = new Container();
bgContainer.addChild(bgBox);
bgContainer.x = 1024;
bgContainer.y = 1366;
game.addChild(bgContainer);
var dayCompleteText = new Text2('DAY COMPLETE', {
size: 64,
fill: '#333333'
});
dayCompleteText.anchor.set(0.5, 0.5);
dayCompleteText.x = 1024;
dayCompleteText.y = 1100;
game.addChild(dayCompleteText);
var finalScoreText = new Text2('Final Score: ' + score, {
size: 48,
fill: '#000000'
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 1024;
finalScoreText.y = 1366;
game.addChild(finalScoreText);
var nextLevelText = '';
if (currentLevel < levelData.length - 1) {
nextLevelText = 'Next: ' + levelData[currentLevel + 1].name;
storage.level = currentLevel + 2;
storage.currentDocIndex = 0;
} else {
nextLevelText = 'Game Complete!';
}
var nextText = new Text2(nextLevelText, {
size: 36,
fill: '#4CAF50'
});
nextText.anchor.set(0.5, 0.5);
nextText.x = 1024;
nextText.y = 1600;
game.addChild(nextText);
}
function processDecision(isApproved) {
var hasErrors = currentDocPair.errors.length > 0;
var isCorrect = false;
if (isApproved) {
isCorrect = !hasErrors;
} else {
isCorrect = hasErrors;
}
if (isCorrect) {
score += 10;
LK.getSound('success').play();
} else {
score -= 10;
LK.getSound('error').play();
}
LK.setScore(score);
loadDocument(currentDocIndex + 1);
}
/****
* Game Update
****/
game.update = function () {
//Game loop updates handled by LK
};
/****
* Event Handlers
****/
game.down = function (x, y, obj) {
if (approveButton && obj === approveButton.children[0]) {
processDecision(true);
} else if (rejectButton && obj === rejectButton.children[0]) {
processDecision(false);
}
};
game.move = function (x, y, obj) {
//Handle drag operations if needed
};
game.up = function (x, y, obj) {
//Handle release operations if needed
};
initializeLevel(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
level: 1,
currentDocIndex: 0
});
/****
* Classes
****/
var DiagramZone = Container.expand(function () {
var self = Container.call(this);
self.zoneText = new Text2('zone', {
size: 24,
fill: '#333333'
});
self.addChild(self.zoneText);
self.interactive = true;
self.linkedFrom = null;
self.zoneId = '';
self.correctWord = '';
self.setup = function (label, posX, posY, zoneId, correctWord) {
self.zoneText.setText(label);
self.x = posX;
self.y = posY;
self.zoneId = zoneId;
self.correctWord = correctWord;
};
self.highlight = function () {
tween(self, {
alpha: 0.6
}, {
duration: 150
});
};
self.unhighlight = function () {
tween(self, {
alpha: 1
}, {
duration: 150
});
};
return self;
});
var DocumentPair = Container.expand(function () {
var self = Container.call(this);
self.scenario = '';
self.umlDiagram = null;
self.errors = [];
self.selectableWords = [];
self.links = [];
self.isApproved = false;
self.isRejected = false;
self.setup = function (scenario, classData, errors) {
self.scenario = scenario;
self.errors = errors;
var textBox = LK.getAsset('textBox', {
anchorX: 0,
anchorY: 0
});
textBox.x = 50;
textBox.y = 50;
self.addChild(textBox);
var scenarioText = new Text2(scenario, {
size: 24,
fill: '#333333'
});
scenarioText.anchor.set(0, 0);
scenarioText.x = 70;
scenarioText.y = 70;
scenarioText.wordWrap = true;
scenarioText.wordWrapWidth = 850;
self.addChild(scenarioText);
var diagramBox = LK.getAsset('diagramBox', {
anchorX: 0,
anchorY: 0
});
diagramBox.x = 50;
diagramBox.y = 480;
self.addChild(diagramBox);
self.umlDiagram = new UMLClassDiagram();
self.umlDiagram.x = 250;
self.umlDiagram.y = 600;
self.umlDiagram.addClassBox(classData.name, classData.properties, classData.methods);
self.addChild(self.umlDiagram);
};
self.approve = function () {
self.isApproved = true;
LK.getSound('stamp').play();
var hasErrors = self.errors.length > 0 && !self.hasFoundAllErrors();
return !hasErrors;
};
self.reject = function () {
self.isRejected = true;
LK.getSound('stamp').play();
var hasErrors = self.errors.length > 0;
return hasErrors;
};
self.hasFoundAllErrors = function () {
return self.links.length >= self.errors.length;
};
return self;
});
var SelectableWord = Container.expand(function () {
var self = Container.call(this);
self.wordText = new Text2('word', {
size: 32,
fill: '#000000'
});
self.addChild(self.wordText);
self.interactive = true;
self.isSelected = false;
self.linkedTo = null;
self.setup = function (word, posX, posY) {
self.wordText.setText(word);
self.x = posX;
self.y = posY;
self.word = word;
};
self.select = function () {
self.isSelected = true;
var highlight = LK.getAsset('selectedWord', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChildAt(highlight, 0);
tween(self, {
alpha: 0.8
}, {
duration: 200
});
};
self.deselect = function () {
self.isSelected = false;
if (self.children.length > 1) {
self.removeChildAt(0);
}
tween(self, {
alpha: 1
}, {
duration: 200
});
};
return self;
});
var UMLClassDiagram = Container.expand(function () {
var self = Container.call(this);
self.className = '';
self.properties = [];
self.methods = [];
self.zones = [];
self.addClassBox = function (classNameStr, properties, methods) {
self.className = classNameStr;
self.properties = properties;
self.methods = methods;
var classGraphics = LK.getAsset('classBox', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(classGraphics);
var titleText = new Text2(classNameStr, {
size: 28,
fill: '#000000'
});
titleText.anchor.set(0.5, 0);
titleText.y = -80;
self.addChild(titleText);
var yOffset = -50;
for (var i = 0; i < properties.length; i++) {
var propText = new Text2('- ' + properties[i].name + ': ' + properties[i].type, {
size: 20,
fill: '#333333'
});
propText.anchor.set(0.5, 0);
propText.y = yOffset;
self.addChild(propText);
var zone = new DiagramZone();
zone.setup(properties[i].name, 0, yOffset, 'prop_' + i, properties[i].name);
self.zones.push(zone);
yOffset += 25;
}
yOffset += 10;
for (var j = 0; j < methods.length; j++) {
var methodText = new Text2('+ ' + methods[j].name + '()', {
size: 20,
fill: '#333333'
});
methodText.anchor.set(0.5, 0);
methodText.y = yOffset;
self.addChild(methodText);
var mzone = new DiagramZone();
mzone.setup(methods[j].name, 0, yOffset, 'method_' + j, methods[j].name);
self.zones.push(mzone);
yOffset += 25;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xffffff
});
/****
* Game Code
****/
/****
* Game Data
****/
var levelData = [{
name: 'Class Basics',
documents: [{
scenario: 'A bank account has a balance and an owner name. It can deposit money and withdraw money.',
classData: {
name: 'BankAccount',
properties: [{
name: 'balance',
type: 'float'
}, {
name: 'owner',
type: 'string'
}],
methods: [{
name: 'deposit',
type: 'void'
}, {
name: 'withdraw',
type: 'void'
}]
},
errors: []
}, {
scenario: 'A car has color, model, and year. It can start engine and stop engine.',
classData: {
name: 'Car',
properties: [{
name: 'color',
type: 'string'
}, {
name: 'model',
type: 'string'
}],
methods: [{
name: 'startEngine',
type: 'void'
}, {
name: 'stopEngine',
type: 'void'
}]
},
errors: [{
type: 'missingProperty',
expected: 'year'
}]
}, {
scenario: 'A student has name, ID, and grade. They can submit assignment and get grades.',
classData: {
name: 'Student',
properties: [{
name: 'name',
type: 'string'
}, {
name: 'studentId',
type: 'int'
}],
methods: [{
name: 'submitAssignment',
type: 'void'
}, {
name: 'getGrades',
type: 'array'
}]
},
errors: [{
type: 'missingProperty',
expected: 'grade'
}]
}, {
scenario: 'A book has title, author, and ISBN. It can be borrowed and returned.',
classData: {
name: 'Book',
properties: [{
name: 'title',
type: 'string'
}, {
name: 'author',
type: 'string'
}, {
name: 'isbn',
type: 'string'
}],
methods: [{
name: 'borrow',
type: 'void'
}, {
name: 'return',
type: 'void'
}]
},
errors: []
}, {
scenario: 'A person has first name, last name, and age. They can walk and eat.',
classData: {
name: 'Person',
properties: [{
name: 'firstName',
type: 'string'
}, {
name: 'lastName',
type: 'string'
}],
methods: [{
name: 'walk',
type: 'void'
}, {
name: 'eat',
type: 'void'
}]
},
errors: [{
type: 'missingProperty',
expected: 'age'
}]
}]
}, {
name: 'Access Modifiers',
documents: [{
scenario: 'A password manager stores encrypted passwords (private). Users can set and verify passwords (public).',
classData: {
name: 'PasswordManager',
properties: [{
name: '- encryptedPassword',
type: 'string'
}, {
name: '+ userId',
type: 'int'
}],
methods: [{
name: '+ setPassword',
type: 'void'
}, {
name: '+ verifyPassword',
type: 'boolean'
}]
},
errors: []
}, {
scenario: 'A database connection should have private host and port. Public methods connect and disconnect.',
classData: {
name: 'DatabaseConnection',
properties: [{
name: '- host',
type: 'string'
}, {
name: '+ port',
type: 'int'
}],
methods: [{
name: '+ connect',
type: 'void'
}, {
name: '+ disconnect',
type: 'void'
}]
},
errors: [{
type: 'wrongAccessModifier',
expected: 'port should be private'
}]
}, {
scenario: 'A calculator performs private validation on inputs. Public methods add, subtract, and multiply.',
classData: {
name: 'Calculator',
properties: [],
methods: [{
name: '+ add',
type: 'double'
}, {
name: '+ subtract',
type: 'double'
}, {
name: '+ multiply',
type: 'double'
}]
},
errors: [{
type: 'missingMethod',
expected: 'validate method should be private'
}]
}, {
scenario: 'A bank account has private balance and private PIN. Public methods deposit and withdraw.',
classData: {
name: 'SecureAccount',
properties: [{
name: '- balance',
type: 'float'
}, {
name: '- pin',
type: 'string'
}],
methods: [{
name: '+ deposit',
type: 'void'
}, {
name: '+ withdraw',
type: 'void'
}]
},
errors: []
}, {
scenario: 'A user profile has public name and email. Password is private. Can update profile publicly.',
classData: {
name: 'UserProfile',
properties: [{
name: '+ name',
type: 'string'
}, {
name: '+ email',
type: 'string'
}, {
name: '- password',
type: 'string'
}],
methods: [{
name: '+ updateProfile',
type: 'void'
}]
},
errors: []
}]
}, {
name: 'Inheritance & Multiplicity',
documents: [{
scenario: 'A company has many employees. Each employee has a name and salary. Employees can work.',
classData: {
name: 'Company',
properties: [{
name: 'name',
type: 'string'
}, {
name: 'employees',
type: 'list<Employee>'
}],
methods: [{
name: 'addEmployee',
type: 'void'
}, {
name: 'getEmployees',
type: 'list<Employee>'
}]
},
errors: []
}, {
scenario: 'A vehicle is a parent class. Cars and motorcycles are vehicles. Vehicles have speed and can stop.',
classData: {
name: 'Vehicle',
properties: [{
name: 'speed',
type: 'double'
}],
methods: [{
name: 'accelerate',
type: 'void'
}, {
name: 'stop',
type: 'void'
}]
},
errors: []
}, {
scenario: 'A library has many books. A book belongs to one library. Books have ISBN and title.',
classData: {
name: 'Library',
properties: [{
name: 'name',
type: 'string'
}, {
name: 'books',
type: 'list<Book>'
}],
methods: [{
name: 'addBook',
type: 'void'
}]
},
errors: [{
type: 'missingProperty',
expected: 'location'
}]
}, {
scenario: 'An animal eats and sleeps. Dogs and cats are animals that can bark and meow.',
classData: {
name: 'Animal',
properties: [{
name: 'name',
type: 'string'
}],
methods: [{
name: 'eat',
type: 'void'
}, {
name: 'sleep',
type: 'void'
}]
},
errors: []
}, {
scenario: 'A school has many students and many teachers. Students study. Teachers teach.',
classData: {
name: 'School',
properties: [{
name: 'name',
type: 'string'
}, {
name: 'students',
type: 'list<Student>'
}, {
name: 'teachers',
type: 'list<Teacher>'
}],
methods: [{
name: 'addStudent',
type: 'void'
}, {
name: 'addTeacher',
type: 'void'
}]
},
errors: []
}]
}];
/****
* Game Variables
****/
var currentLevel = storage.level - 1;
var currentDocIndex = storage.currentDocIndex;
var currentDocPair = null;
var selectedWord = null;
var score = LK.getScore();
var documents = [];
var approveButton = null;
var rejectButton = null;
var rulePanelVisible = false;
var rulePanel = null;
/****
* Helper Functions
****/
function initializeLevel() {
var level = levelData[currentLevel];
documents = [];
for (var i = 0; i < level.documents.length; i++) {
var doc = new DocumentPair();
doc.setup(level.documents[i].scenario, level.documents[i].classData, level.documents[i].errors);
documents.push(doc);
}
loadDocument(0);
}
function loadDocument(docIndex) {
if (docIndex >= documents.length) {
showDayComplete();
return;
}
currentDocIndex = docIndex;
currentDocPair = documents[docIndex];
game.removeChildren();
game.addChild(currentDocPair);
createControlUI();
createRulePanel();
}
function createControlUI() {
var approveGraphics = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
});
approveButton = new Container();
approveButton.addChild(approveGraphics);
approveButton.interactive = true;
var approveText = new Text2('APPROVE', {
size: 32,
fill: '#ffffff'
});
approveText.anchor.set(0.5, 0.5);
approveButton.addChild(approveText);
approveButton.x = 1850;
approveButton.y = 1400;
game.addChild(approveButton);
var rejectGraphics = LK.getAsset('rejectButton', {
anchorX: 0.5,
anchorY: 0.5
});
rejectButton = new Container();
rejectButton.addChild(rejectGraphics);
rejectButton.interactive = true;
var rejectText = new Text2('REJECT', {
size: 32,
fill: '#ffffff'
});
rejectText.anchor.set(0.5, 0.5);
rejectButton.addChild(rejectText);
rejectButton.x = 1850;
rejectButton.y = 1550;
game.addChild(rejectButton);
var scoreText = new Text2('Score: ' + score, {
size: 40,
fill: '#000000'
});
scoreText.anchor.set(0, 0);
scoreText.x = 50;
scoreText.y = 1900;
game.addChild(scoreText);
scoreText.scoreDisplay = true;
var docCountText = new Text2('Document ' + (currentDocIndex + 1) + ' / ' + documents.length, {
size: 32,
fill: '#666666'
});
docCountText.anchor.set(0, 0);
docCountText.x = 50;
docCountText.y = 1960;
game.addChild(docCountText);
}
function createRulePanel() {
var level = levelData[currentLevel];
var ruleGraphics = LK.getAsset('rulePanel', {
anchorX: 0,
anchorY: 0
});
rulePanel = new Container();
rulePanel.addChild(ruleGraphics);
rulePanel.x = 1050;
rulePanel.y = 50;
var ruleTitle = new Text2('RULES', {
size: 28,
fill: '#333333'
});
ruleTitle.anchor.set(0.5, 0);
ruleTitle.x = 150;
ruleTitle.y = 20;
rulePanel.addChild(ruleTitle);
var ruleText = '';
if (currentLevel === 0) {
ruleText = 'L1: Basic Classes\n\nVerify properties match the description\n\nVerify methods match the description';
} else if (currentLevel === 1) {
ruleText = 'L2: Access Modifiers\n\n+ = public\n- = private\n\nData should be private\nMethods should be public';
} else {
ruleText = 'L3: Inheritance\n\nParent classes define shared behavior\n\nMultiplicity: one-to-many relationships';
}
var ruleContentText = new Text2(ruleText, {
size: 18,
fill: '#555555'
});
ruleContentText.anchor.set(0, 0);
ruleContentText.x = 20;
ruleContentText.y = 70;
ruleContentText.wordWrap = true;
ruleContentText.wordWrapWidth = 260;
rulePanel.addChild(ruleContentText);
game.addChild(rulePanel);
}
function showDayComplete() {
game.removeChildren();
var bgBox = LK.getAsset('diagramBox', {
anchorX: 0.5,
anchorY: 0.5
});
var bgContainer = new Container();
bgContainer.addChild(bgBox);
bgContainer.x = 1024;
bgContainer.y = 1366;
game.addChild(bgContainer);
var dayCompleteText = new Text2('DAY COMPLETE', {
size: 64,
fill: '#333333'
});
dayCompleteText.anchor.set(0.5, 0.5);
dayCompleteText.x = 1024;
dayCompleteText.y = 1100;
game.addChild(dayCompleteText);
var finalScoreText = new Text2('Final Score: ' + score, {
size: 48,
fill: '#000000'
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 1024;
finalScoreText.y = 1366;
game.addChild(finalScoreText);
var nextLevelText = '';
if (currentLevel < levelData.length - 1) {
nextLevelText = 'Next: ' + levelData[currentLevel + 1].name;
storage.level = currentLevel + 2;
storage.currentDocIndex = 0;
} else {
nextLevelText = 'Game Complete!';
}
var nextText = new Text2(nextLevelText, {
size: 36,
fill: '#4CAF50'
});
nextText.anchor.set(0.5, 0.5);
nextText.x = 1024;
nextText.y = 1600;
game.addChild(nextText);
}
function processDecision(isApproved) {
var hasErrors = currentDocPair.errors.length > 0;
var isCorrect = false;
if (isApproved) {
isCorrect = !hasErrors;
} else {
isCorrect = hasErrors;
}
if (isCorrect) {
score += 10;
LK.getSound('success').play();
} else {
score -= 10;
LK.getSound('error').play();
}
LK.setScore(score);
loadDocument(currentDocIndex + 1);
}
/****
* Game Update
****/
game.update = function () {
//Game loop updates handled by LK
};
/****
* Event Handlers
****/
game.down = function (x, y, obj) {
if (approveButton && obj === approveButton.children[0]) {
processDecision(true);
} else if (rejectButton && obj === rejectButton.children[0]) {
processDecision(false);
}
};
game.move = function (x, y, obj) {
//Handle drag operations if needed
};
game.up = function (x, y, obj) {
//Handle release operations if needed
};
initializeLevel();