Compare commits
3 commits
master
...
no-js-rend
Author | SHA1 | Date | |
---|---|---|---|
|
a93aadf0fa | ||
|
f7f20edb70 | ||
|
d8733f0add |
3
.gitignore
vendored
|
@ -105,6 +105,3 @@ Dockerfile
|
|||
# coffeelint for editors (might be standardized eventually)
|
||||
coffeelint.json
|
||||
gitSpy/*
|
||||
|
||||
# nightwatch reports
|
||||
spec/smoke/reports/
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
instrumentation:
|
||||
root: ./server
|
|
@ -4,18 +4,13 @@ language: node_js
|
|||
|
||||
node_js:
|
||||
- 5.1.1
|
||||
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- mongodb-upstart
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- mongodb-org-server
|
||||
- g++-4.8
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
@ -36,10 +31,6 @@ before_script:
|
|||
script:
|
||||
- "./node_modules/karma/bin/karma start --browsers Firefox --single-run --reporters dots"
|
||||
- "npm run jasmine"
|
||||
- "npm run coverage"
|
||||
|
||||
after_script:
|
||||
- "npm install coveralls && cat ./coverage/lcov.info | coveralls"
|
||||
|
||||
notifications:
|
||||
slack:
|
||||
|
|
11
README.md
|
@ -9,7 +9,8 @@
|
|||
|
||||
CodeCombat is a multiplayer programming game for learning how to code.
|
||||
**See the [Archmage (coder) developer wiki](../../wiki/Archmage-Home) for a dev
|
||||
setup guide, extensive documentation, and much more to get started hacking!**
|
||||
setup guide, extensive documentation, and much more. Every new person that wants
|
||||
to start contributing the project coding should start there.**
|
||||
|
||||
It's both a startup and a community project, completely open source under the
|
||||
[MIT and Creative Commons licenses](http://codecombat.com/legal). It's the
|
||||
|
@ -45,11 +46,11 @@ so we can accept your pull requests. It is easy.
|
|||
|
||||
### [Join Us!](http://blog.codecombat.com/why-you-should-open-source-your-startup)
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
|
BIN
app/assets/images/jquery.minicolors.png
Normal file
After ![]() (image error) Size: 68 KiB |
Before ![]() (image error) Size: 6.7 KiB After ![]() (image error) Size: 6.7 KiB ![]() ![]() |
BIN
app/assets/images/pages/about/desert.png
Normal file → Executable file
Before ![]() (image error) Size: 486 KiB After ![]() (image error) Size: 344 KiB ![]() ![]() |
BIN
app/assets/images/pages/about/elliot_small.png
Normal file
After ![]() (image error) Size: 23 KiB |
Before ![]() (image error) Size: 8.6 KiB After ![]() (image error) Size: 8.6 KiB ![]() ![]() |
BIN
app/assets/images/pages/about/lisa_small.png
Normal file
After ![]() (image error) Size: 20 KiB |
Before ![]() (image error) Size: 8.2 KiB After ![]() (image error) Size: 8.2 KiB ![]() ![]() |
BIN
app/assets/images/pages/about/matt_small.png
Normal file
After ![]() (image error) Size: 21 KiB |
Before ![]() (image error) Size: 55 KiB |
Before ![]() (image error) Size: 54 KiB |
BIN
app/assets/images/pages/about/nick_small.png
Normal file
After ![]() (image error) Size: 8.1 KiB |
Before ![]() (image error) Size: 18 KiB After ![]() (image error) Size: 18 KiB ![]() ![]() |
Before ![]() (image error) Size: 22 KiB After ![]() (image error) Size: 22 KiB ![]() ![]() |
Before ![]() (image error) Size: 6.8 KiB After ![]() (image error) Size: 6.8 KiB ![]() ![]() |
BIN
app/assets/images/pages/about/robin_small.png
Normal file
After ![]() (image error) Size: 15 KiB |
Before ![]() (image error) Size: 8 KiB After ![]() (image error) Size: 8 KiB ![]() ![]() |
BIN
app/assets/images/pages/about/screenshot_desert.png
Normal file → Executable file
Before ![]() (image error) Size: 112 KiB After ![]() (image error) Size: 90 KiB ![]() ![]() |
Before ![]() (image error) Size: 22 KiB |
Before ![]() (image error) Size: 18 KiB |
Before ![]() (image error) Size: 20 KiB |
Before ![]() (image error) Size: 11 KiB |
Before ![]() (image error) Size: 17 KiB |
Before ![]() (image error) Size: 22 KiB |
Before ![]() (image error) Size: 21 KiB |
Before ![]() (image error) Size: 22 KiB |
Before ![]() (image error) Size: 26 KiB |
Before ![]() (image error) Size: 20 KiB |
Before ![]() (image error) Size: 22 KiB |
Before ![]() (image error) Size: 23 KiB |
Before ![]() (image error) Size: 24 KiB |
Before ![]() (image error) Size: 22 KiB |
Before ![]() (image error) Size: 23 KiB |
Before ![]() (image error) Size: 22 KiB |
Before ![]() (image error) Size: 21 KiB |
Before ![]() (image error) Size: 25 KiB |
Before ![]() (image error) Size: 23 KiB |
Before ![]() (image error) Size: 43 KiB After ![]() (image error) Size: 59 KiB ![]() ![]() |
Before ![]() (image error) Size: 24 KiB |
Before ![]() (image error) Size: 34 KiB After ![]() (image error) Size: 34 KiB ![]() ![]() |
Before ![]() (image error) Size: 33 KiB After ![]() (image error) Size: 33 KiB ![]() ![]() |
Before ![]() (image error) Size: 24 KiB After ![]() (image error) Size: 24 KiB ![]() ![]() |
Before ![]() (image error) Size: 27 KiB After ![]() (image error) Size: 27 KiB ![]() ![]() |
Before ![]() (image error) Size: 38 KiB After ![]() (image error) Size: 38 KiB ![]() ![]() |
Before ![]() (image error) Size: 486 KiB After ![]() (image error) Size: 108 KiB ![]() ![]() |
Before ![]() (image error) Size: 26 KiB |
Before ![]() (image error) Size: 26 KiB |
Before ![]() (image error) Size: 34 KiB |
Before ![]() (image error) Size: 20 KiB |
Before ![]() (image error) Size: 20 KiB |
Before ![]() (image error) Size: 27 KiB |
Before ![]() (image error) Size: 11 KiB |
Before ![]() (image error) Size: 317 KiB |
|
@ -1,201 +0,0 @@
|
|||
// TODO: don't serve this script from codecombat.com; serve it from a harmless extra domain we don't have yet.
|
||||
|
||||
var lastSource = null;
|
||||
var lastOrigin = null;
|
||||
window.onerror = function(message, url, line, column, error){
|
||||
console.log("User script error on line " + line + ", column " + column + ": ", error);
|
||||
lastSource.postMessage({ type: 'error', message: message, url: url, line: line, column: column }, lastOrigin);
|
||||
}
|
||||
window.addEventListener('message', receiveMessage, false);
|
||||
|
||||
var concreteDom;
|
||||
var concreteStyles;
|
||||
var concreteScripts;
|
||||
var virtualDom;
|
||||
var virtualStyles;
|
||||
var virtualScripts;
|
||||
var goalStates;
|
||||
|
||||
var allowedOrigins = [
|
||||
/^https?:\/\/(.*\.)?codecombat\.com$/,
|
||||
/^https?:\/\/localhost:3000$/,
|
||||
/^https?:\/\/.*codecombat-staging-codecombat\.runnableapp\.com$/,
|
||||
];
|
||||
|
||||
function receiveMessage(event) {
|
||||
var origin = event.origin || event.originalEvent.origin; // For Chrome, the origin property is in the event.originalEvent object.
|
||||
var allowed = false;
|
||||
allowedOrigins.forEach(function(pattern) {
|
||||
allowed = allowed || pattern.test(origin);
|
||||
});
|
||||
if (!allowed) {
|
||||
console.log('Ignoring message from bad origin:', origin);
|
||||
return;
|
||||
}
|
||||
lastOrigin = origin;
|
||||
var data = event.data;
|
||||
var source = lastSource = event.source;
|
||||
switch (data.type) {
|
||||
case 'create':
|
||||
create(_.pick(data, 'dom', 'styles', 'scripts'));
|
||||
checkGoals(data.goals, source, origin);
|
||||
$('body').first().off('click', checkRememberedGoals);
|
||||
$('body').first().on('click', checkRememberedGoals);
|
||||
break;
|
||||
case 'update':
|
||||
if (virtualDom)
|
||||
update(_.pick(data, 'dom', 'styles', 'scripts'));
|
||||
else
|
||||
create(_.pick(data, 'dom', 'styles', 'scripts'));
|
||||
checkGoals(data.goals, source, origin);
|
||||
break;
|
||||
case 'log':
|
||||
console.log(data.text);
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown message type:', data.type);
|
||||
}
|
||||
}
|
||||
|
||||
function create(options) {
|
||||
virtualDom = options.dom;
|
||||
virtualStyles = options.styles;
|
||||
virtualScripts = options.scripts;
|
||||
concreteDom = deku.dom.create(virtualDom);
|
||||
concreteStyles = deku.dom.create(virtualStyles);
|
||||
concreteScripts = deku.dom.create(virtualScripts);
|
||||
// TODO: :after elements don't seem to work? (:before do)
|
||||
$('body').first().empty().append(concreteDom);
|
||||
replaceNodes('[for="player-styles"]', unwrapConcreteNodes(concreteStyles));
|
||||
replaceNodes('[for="player-scripts"]', unwrapConcreteNodes(concreteScripts));
|
||||
}
|
||||
|
||||
function unwrapConcreteNodes(wrappedNodes) {
|
||||
return wrappedNodes.children;
|
||||
}
|
||||
|
||||
function replaceNodes(selector, newNodes){
|
||||
$newNodes = $(newNodes).clone();
|
||||
$(selector + ':not(:first)').remove();
|
||||
|
||||
firstNode = $(selector).first();
|
||||
$newNodes.attr('for', firstNode.attr('for'));
|
||||
|
||||
newFirstNode = $newNodes[0];
|
||||
firstNode.replaceWith(newFirstNode); // Removes newFirstNode from its array (!!)
|
||||
|
||||
$(newFirstNode).after($newNodes);
|
||||
}
|
||||
|
||||
function update(options) {
|
||||
var dom = options.dom;
|
||||
var styles = options.styles;
|
||||
var scripts = options.scripts;
|
||||
function dispatch() {} // Might want to do something here in the future
|
||||
var context = {}; // Might want to use this to send shared state to every component
|
||||
|
||||
var domChanges = deku.diff.diffNode(virtualDom, dom);
|
||||
domChanges.reduce(deku.dom.update(dispatch, context), concreteDom); // Rerender
|
||||
|
||||
// var scriptChanges = deku.diff.diffNode(virtualScripts, scripts);
|
||||
// scriptChanges.reduce(deku.dom.update(dispatch, context), concreteScripts); // Rerender
|
||||
// replaceNodes('[for="player-scripts"]', unwrapConcreteNodes(concreteScripts));
|
||||
|
||||
var styleChanges = deku.diff.diffNode(virtualStyles, styles);
|
||||
styleChanges.reduce(deku.dom.update(dispatch, context), concreteStyles); // Rerender
|
||||
replaceNodes('[for="player-styles"]', unwrapConcreteNodes(concreteStyles));
|
||||
|
||||
virtualDom = dom;
|
||||
virtualStyles = styles;
|
||||
virtualScripts = scripts;
|
||||
}
|
||||
|
||||
var lastGoalArgs = [];
|
||||
function checkRememberedGoals() {
|
||||
checkGoals.apply(this, lastGoalArgs);
|
||||
}
|
||||
|
||||
function checkGoals(goals, source, origin) {
|
||||
lastGoalArgs = [goals, source, origin]; // Memoize for checkRememberedGoals
|
||||
// Check right now and also in one second, since our 1-second CSS transition might be affecting things until it is done.
|
||||
doCheckGoals(goals, source, origin);
|
||||
_.delay(function() { doCheckGoals(goals, source, origin); }, 1001);
|
||||
}
|
||||
|
||||
function doCheckGoals(goals, source, origin) {
|
||||
var newGoalStates = {};
|
||||
var overallSuccess = true;
|
||||
goals.forEach(function(goal) {
|
||||
var $result = $(goal.html.selector);
|
||||
//console.log('ran selector', goal.html.selector, 'to find element(s)', $result);
|
||||
var success = true;
|
||||
goal.html.valueChecks.forEach(function(check) {
|
||||
//console.log(' ... and should make sure that the value of', check.eventProps, 'is', _.omit(check, 'eventProps'), '?', matchesCheck($result, check))
|
||||
success = success && matchesCheck($result, check);
|
||||
});
|
||||
overallSuccess = overallSuccess && success;
|
||||
newGoalStates[goal.id] = {status: success ? 'success' : 'incomplete'}; // No 'failure' state
|
||||
});
|
||||
if (!_.isEqual(newGoalStates, goalStates)) {
|
||||
goalStates = newGoalStates;
|
||||
var overallStatus = overallSuccess ? 'success' : null; // Can't really get to 'failure', just 'incomplete', which is represented by null here
|
||||
source.postMessage({type: 'goals-updated', goalStates: goalStates, overallStatus: overallStatus}, origin);
|
||||
}
|
||||
}
|
||||
|
||||
function downTheChain(obj, keyChain) {
|
||||
if (!obj)
|
||||
return null;
|
||||
if (!_.isArray(keyChain))
|
||||
return obj[keyChain];
|
||||
var value = obj;
|
||||
while (keyChain.length && value) {
|
||||
if (keyChain[0].match(/\(.*\)$/)) {
|
||||
var args, argsString = keyChain[0].match(/\((.*)\)$/)[1];
|
||||
if (argsString)
|
||||
args = eval(argsString).split(/, ?/g).filter(function(x) { return x !== ''; }); // TODO: can/should we avoid eval here?
|
||||
else
|
||||
args = [];
|
||||
value = value[keyChain[0].split('(')[0]].apply(value, args); // value.text(), value.css('background-color'), etc.
|
||||
}
|
||||
else
|
||||
value = value[keyChain[0]];
|
||||
keyChain = keyChain.slice(1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function matchesCheck(value, check) {
|
||||
var v = downTheChain(value, check.eventProps);
|
||||
if ((check.equalTo != null) && v !== check.equalTo) {
|
||||
return false;
|
||||
}
|
||||
if ((check.notEqualTo != null) && v === check.notEqualTo) {
|
||||
return false;
|
||||
}
|
||||
if ((check.greaterThan != null) && !(v > check.greaterThan)) {
|
||||
return false;
|
||||
}
|
||||
if ((check.greaterThanOrEqualTo != null) && !(v >= check.greaterThanOrEqualTo)) {
|
||||
return false;
|
||||
}
|
||||
if ((check.lessThan != null) && !(v < check.lessThan)) {
|
||||
return false;
|
||||
}
|
||||
if ((check.lessThanOrEqualTo != null) && !(v <= check.lessThanOrEqualTo)) {
|
||||
return false;
|
||||
}
|
||||
if ((check.containingString != null) && (!v || v.search(check.containingString) === -1)) {
|
||||
return false;
|
||||
}
|
||||
if ((check.notContainingString != null) && (v != null ? v.search(check.notContainingString) : void 0) !== -1) {
|
||||
return false;
|
||||
}
|
||||
if ((check.containingRegexp != null) && (!v || v.search(new RegExp(check.containingRegexp)) === -1)) {
|
||||
return false;
|
||||
}
|
||||
if ((check.notContainingRegexp != null) && (v != null ? v.search(new RegExp(check.notContainingRegexp)) : void 0) !== -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -19,7 +19,6 @@ var languagesImported = {};
|
|||
|
||||
var ensureLanguageImported = function(language) {
|
||||
if (languagesImported[language]) return;
|
||||
if (language === 'html') return;
|
||||
importScripts("/javascripts/app/vendor/aether-" + language + ".js");
|
||||
languagesImported[language] = true;
|
||||
};
|
||||
|
|
|
@ -80,7 +80,7 @@ var myImportScripts = importScripts;
|
|||
var languagesImported = {};
|
||||
var ensureLanguageImported = function(language) {
|
||||
if (languagesImported[language]) return;
|
||||
if (language === 'javascript' || language === 'html') return; // Only has JSHint, but we don't need to lint here.
|
||||
if (language === 'javascript') return; // Only has JSHint, but we don't need to lint here.
|
||||
myImportScripts("/javascripts/app/vendor/aether-" + language + ".js");
|
||||
languagesImported[language] = true;
|
||||
};
|
||||
|
@ -389,8 +389,6 @@ self.runWorld = function runWorld(args) {
|
|||
self.world.preloading = args.preload;
|
||||
self.world.headless = args.headless;
|
||||
self.world.realTime = args.realTime;
|
||||
self.world.indefiniteLength = args.indefiniteLength;
|
||||
self.world.justBegin = args.justBegin;
|
||||
self.goalManager = new GoalManager(self.world);
|
||||
self.goalManager.setGoals(args.goals);
|
||||
self.goalManager.setCode(args.userCodeMap);
|
||||
|
@ -436,9 +434,6 @@ self.onWorldLoaded = function onWorldLoaded() {
|
|||
var diff = t1 - self.t0;
|
||||
var goalStates = self.goalManager.getGoalStates();
|
||||
var totalFrames = self.world.totalFrames;
|
||||
if(self.world.indefiniteLength) {
|
||||
totalFrames = self.world.frames.length;
|
||||
}
|
||||
if(self.world.ended) {
|
||||
var overallStatus = self.goalManager.checkOverallStatus();
|
||||
var lastFrameHash = self.world.frames[totalFrames - 2].hash
|
||||
|
@ -507,11 +502,6 @@ self.onWorldError = function onWorldError(error) {
|
|||
}
|
||||
else {
|
||||
console.log("Non-UserCodeError:", error.toString() + "\n" + error.stack || error.stackTrace);
|
||||
if(self.world.indefiniteLength) {
|
||||
// We don't abort completely, since the player can always click to end the game.
|
||||
// TODO: some better error to the user would be nice, though.
|
||||
return true;
|
||||
}
|
||||
self.postMessage({type: 'non-user-code-problem', problem: {message: error.toString()}});
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,411 +0,0 @@
|
|||
###### Last updated: 08/23/2016
|
||||
|
||||
##### Lesson Plans
|
||||
# Introduction to Computer Science
|
||||
|
||||
### Curriculum Summary
|
||||
- Level: Beginner
|
||||
- 4 x 45-60 minute coding sessions
|
||||
|
||||
|
||||
#### Overview
|
||||
With the right environment, learning the basics of formal syntax and typing code can be fun and intuitive for students as early as 3rd grade. Instead of block-based visual programming languages that hinder a student’s proper understanding of code, CodeCombat introduces real coding from the very first level. By strengthening their typing, syntax and debugging skills, we empower students to feel capable of building real programs successfully.
|
||||
|
||||
_This guide is written with Python-language classrooms in mind, but can easily be adapted for JavaScript._
|
||||
|
||||
### Scope and Sequence
|
||||
|
||||
| Module | Levels | Transfer Goals |
|
||||
| ---------------------------------------------------------- | :----------------- | :--------------------------- |
|
||||
| [1. Basic Syntax](#basic-syntax) | 1-6 | Call functions in order |
|
||||
| [2. Loops](#loops) | 7-14 | Repeat code sequences |
|
||||
| [3. Variables](#variables) | 15-20 | Save and access data |
|
||||
| [4. Review - Multiplayer Arena](#review-multiplayer-arena) | 21 | Master syntax and sequencing |
|
||||
|
||||
### Core Vocabulary
|
||||
|
||||
**Basic Syntax** - the basic spelling and grammar of a language, and must be carefully paid attention to in order for code to properly execute. For example, while Python and JavaScript are used to do similar things in Course 1, the syntax for them is noticeably different, because they are different programming languages.
|
||||
|
||||
**Object** - a character or thing that can perform actions.
|
||||
|
||||
**String** - a type of programming data that represents text. In both Python and JavaScript, strings are represented by text inside quotes. In Course 1, strings are used to identify objects for the hero to attack.
|
||||
|
||||
**Function** - an action performed by an object.
|
||||
|
||||
**Argument** - extra information passed into a method in order to modify what the method does. In both Python and JavaScript, arguments are represented by code that is inside the parentheses after a method. In Course 1, arguments must be used to define enemies before the hero can attack them, and can also be used to move multiple times without writing new lines of code.
|
||||
|
||||
**Property** - data about or belonging to an object.
|
||||
|
||||
**While Loop** - used to repeat actions without the player needing to write the same lines of code over and over. In Python, the code that is looped must be indented underneath the while true statement. In JavaScript, the code that is looped must be enclosed by curly brackets {}. In Course 1, while loops repeat forever, and are used to navigate mazes made up of identical paths, as well as attack objects that take a lot of hits to defeat (strong Doors, for example).
|
||||
|
||||
**Variable** - a symbol that represents data, and the value of the variable can change as you store new data in it. In Course 1, variables are used to first define an enemy, and then passed along as an argument to the attack method so that the hero can attack the right enemy.
|
||||
|
||||
##### Module 1
|
||||
## Basic Syntax
|
||||
### Summary
|
||||
|
||||
The puzzles in these levels are framed as mazes for students to solve using Computational Thinking and computer programming. They are designed to be a gentle introduction to Python syntax through a relatable medium.
|
||||
|
||||
The hero starts at a particular place and has to navigate to the goal without running into spikes or being spotted by ogres.
|
||||
|
||||
Some students may want to delete their code every time and only type the next step. Explain to them that the code must contain all the instructions from start to finish, like a story: it has a beginning, a middle, and an end. Every time you click Start, the hero returns to the beginning.
|
||||
|
||||
### Transfer goals
|
||||
|
||||
- Use Python syntax
|
||||
- Call functions
|
||||
- Understand that order matters
|
||||
|
||||
### Standards
|
||||
|
||||
**CCSS.Math.Practice.MP1** Make sense of problems and persevere in solving them.
|
||||
**CCSS.Math.Practice.MP6** Attend to precision.
|
||||
|
||||
### Instructive Activity: Basic Syntax (10 mins)
|
||||
|
||||
#### Explain (3 mins)
|
||||
|
||||
**Syntax** is how we write code. Just like spelling and grammar are important in writing prose, syntax is important when writing code. Humans are good at figuring out what something means, even if it isn’t exactly correct, but computers aren’t that smart, and they need you to write with no mistakes.
|
||||
|
||||
code example: `hero.moveRight()`
|
||||
vocabulary: (object) (function)
|
||||
read aloud: “hero dot move right”
|
||||
|
||||
**Objects** are the building blocks of Python. They are things or characters that can perform actions. Your hero is an object. It can perform the moving actions.
|
||||
|
||||
**Functions** are actions an object can do. `moveRight()` is a function. Function names are always followed by parentheses. The order of functions matters!
|
||||
|
||||
#### Interact (5 mins): Recycling Robot
|
||||
|
||||
Practice giving written instructions using Python functions in order.
|
||||
|
||||
**Materials:** Desk or table, recycling bin, balls of paper to recycle
|
||||
|
||||
You (the teacher) are going to be the robot that the class controls using functions. The goal of this exercise is for the class to collectively write a program like this:
|
||||
|
||||
``` python
|
||||
teacher.pickUpBall()
|
||||
teacher.turnRight()
|
||||
teacher.moveForward()
|
||||
teacher.moveForward()
|
||||
teacher.turnLeft()
|
||||
teacher.moveForward()
|
||||
teacher.dropBall()
|
||||
```
|
||||
|
||||
The experience should introduce them to Python syntax (including the dot between the object and function, and the parentheses at the end) and the importance of order in a sequence of instructions.
|
||||
|
||||
At the front of the class, set some scrunched up paper balls on a flat surface. Place the recycling bin a few steps away. Explain that you are a recycling robot, and the class’s job is to program you.
|
||||
|
||||
The robot is a Python object. What is your name in Python? Whatever you choose, make sure it starts with a lower-case letter. Write it on the board.
|
||||
|
||||
`teacher`
|
||||
|
||||
To make the robot perform an action, you have to call a function. Write a dot after your object name, then decide as a class what the first action should be. After the dot, write the function name followed by empty parentheses. Off to one side, draw a “Run” button.
|
||||
|
||||
`teacher.pickUpBall()`
|
||||
|
||||
Have a volunteer press the “Run” button to run the program and test that it works.
|
||||
|
||||
_It is important that you reset yourself and the paper balls every time the code is changed, and run the whole program from the beginning._
|
||||
|
||||
Invite students to add code to the program one at a time. If there is an error in the syntax, make a funny beeping sound and stop. Have the class work together to write and rewrite the program until you successfully get a ball in the recycling bin.
|
||||
|
||||
#### Reflect (2 mins)
|
||||
|
||||
**Why is syntax important?** (It lets you be specific about exactly what you want to happen.)
|
||||
|
||||
**Does order matter?** (yes)
|
||||
|
||||
**Can a human understand the directions even if there’s a mistake in the syntax?** (sometimes)
|
||||
|
||||
**Can a computer?** (no)
|
||||
|
||||
### Coding Time (30-45 mins)
|
||||
|
||||
**First time students will need to create accounts**
|
||||
For additional information on helping students create account, see our [Teacher Getting Started Guide](http://files.codecombat.com/docs/resources/TeacherGettingStartedGuide.pdf).
|
||||
|
||||
Allow the students to go through the game at their own pace, keeping notes about every level on paper or digital document. We recommend using following format, which you can also print out as templates: [Progress Journal [PDF]](http://files.codecombat.com/docs/resources/ProgressJournal.pdf)
|
||||
|
||||
```
|
||||
Level #: _____ Level Name: ____________________________________
|
||||
Goal: __________________________________________________________
|
||||
What I did:
|
||||
|
||||
What I learned:
|
||||
|
||||
What was challenging:
|
||||
|
||||
|
||||
```
|
||||
|
||||
Circulate to assist. Draw students’ attention to the instructions and tips.
|
||||
|
||||
If student have trouble breaking the problem down, refer to the [Engineering Cycle Worksheet [PDF]](http://files.codecombat.com/docs/resources/EngineeringCycleWorksheet.pdf) to reinforce the steps to solving each puzzle.
|
||||
|
||||
### Written reflection (5 mins)
|
||||
|
||||
Select appropriate prompt(s) for the students respond to, referring to their notes.
|
||||
|
||||
**Tell me how to play CodeCombat.**
|
||||
|
||||
>You have to move to the gem without hitting the spikes. I learned that you have to type “hero.” then the moving code. You have to spell it right and put () at the end. But it shows you the things you can type and you can click on them instead. You click RUN to make it go. You can try as many times as you need.
|
||||
|
||||
**What’s the difference between an object and a function?**
|
||||
|
||||
>The object is the hero and she has functions that are things she can do. The object has a dot after it and the function has ().
|
||||
|
||||
**How can you tell when you’ve made a mistake in your code? How do you fix it?**
|
||||
|
||||
>Sometimes the code doesn’t won’t run because there is a mistake in it. They put a red ! next to the mistake and try to help you. You have to read the code to figure out what’s wrong.
|
||||
|
||||
**Why is your hero in the Kithgard Dungeon? What is your quest? Are you a good guy or a bad guy?**
|
||||
_(write your own backstory)_
|
||||
|
||||
>I went into the Kithgard Dungeon to steal gems from the ogres. I need to get a lot of gems to pay the ransom for my village, otherwise a big bully monster will destroy it and my family will be homeless. I think I’m a good guy but the ogres probably think I’m bad because I’m stealing from them.
|
||||
|
||||
|
||||
##### Module 2
|
||||
## Loops
|
||||
|
||||
### Summary
|
||||
|
||||
Up to now, students have had to write long sequences of actions with no shortcuts. These levels introduce loops, which allow them to achieve more with fewer lines of code.
|
||||
|
||||
The puzzles in this section are harder to solve than in the first module. Encourage collaboration among your students, as they first must understand what their goal is, then devise a strategy for solving the level, then put that plan into action.
|
||||
|
||||
### Transfer Goals
|
||||
|
||||
- Write an infinite loop
|
||||
- Break a problem into smaller pieces
|
||||
- Decide which parts of an action repeat
|
||||
|
||||
### Standards
|
||||
**CCSS.Math.Practice.MP1** Make sense of problems and persevere in solving them.
|
||||
**CCSS.Math.Practice.MP8** Look for and express regularity in repeated reasoning.
|
||||
|
||||
### Instructive Activity: Loops (10 mins)
|
||||
|
||||
#### Explain (3 mins)
|
||||
|
||||
A **loop** is a way of repeating code. One way of writing loops uses the keyword _while,_ followed by an **expression** that can be evaluated as True or False. _while_ is a special word that tells the computer to evaluate (or solve) what comes after it, and then do the actions indented underneath until the expression becomes False.
|
||||
|
||||
These levels in CodeCombat require an **infinite loop**, or a loop that repeats forever. For that, we need an expression that is always true. Luckily, _True_ is a Python shortcut that always evaluates as True!
|
||||
|
||||
Below, `while` is the keyword, and `True` is the expression
|
||||
``` python
|
||||
while True:
|
||||
hero.moveRight() # action
|
||||
hero.moveUp() # another action
|
||||
```
|
||||
|
||||
You can put as many lines of code as you want inside the loop. They all have to be indented with four spaces. That’s how Python knows they’re part of the loop. Indentation is an important part of Python! Whenever you have a problem with your code, check the indentation first.
|
||||
|
||||
#### Interact (5 mins)
|
||||
|
||||
As a class, think of as many ways as possible of writing a repeating action in English. (Use the following examples if students have a hard time thinking of their own.)
|
||||
|
||||
Circle the English words that tell you it’s a loop. Rewrite these instructions using `while`. Check indentation. Label each part as keyword, expression, or action. Here are some examples to get you started:
|
||||
|
||||
Keep walking **until** you get to the door. _While you are not at the door, keep walking._
|
||||
``` python
|
||||
while door = 0:
|
||||
walk()
|
||||
```
|
||||
|
||||
Bounce the ball five **times**. _While bounces are less than 5, bounce the ball._
|
||||
|
||||
``` python
|
||||
while bounces < 5 :
|
||||
ball.bounce()
|
||||
```
|
||||
|
||||
Put away **every** toy. _While there are still toys out, put a toy away._
|
||||
|
||||
``` python
|
||||
while toys > 0:
|
||||
putAway(toy)
|
||||
```
|
||||
|
||||
Have students take turns writing, checking, and labelling the code until it becomes easy.
|
||||
|
||||
#### Reflect (2 mins)
|
||||
|
||||
**What is a loop?** (a way of repeating actions)
|
||||
|
||||
**What is an expression?** (something that is True or False, usually using =, <, or >)
|
||||
|
||||
**How do you write a loop that never ends?** (Use `while True`)
|
||||
|
||||
### Coding Time (30-45 mins)
|
||||
|
||||
Allow the students to go through the game at their own pace, keeping notes about every level on paper or digital document. We recommend using following format, which you can also print out as templates: [Progress Journal [PDF]](http://files.codecombat.com/docs/resources/ProgressJournal.pdf)
|
||||
|
||||
```
|
||||
Level #: _____ Level Name: ____________________________________
|
||||
Goal: __________________________________________________________
|
||||
What I did:
|
||||
|
||||
What I learned:
|
||||
|
||||
What was challenging:
|
||||
|
||||
|
||||
```
|
||||
|
||||
Circulate to assist. Draw students’ attention to the instructions and tips.
|
||||
|
||||
Focus on debugging, employing appropriate strategies for getting yourself unstuck. Use your class/school’s growth mindset guidelines, use the [Engineering Cycle Worksheet [PDF]](http://files.codecombat.com/docs/resources/EngineeringCycleWorksheet.pdf) as an unblocking tool, or ask them to follow this list:
|
||||
|
||||
1. Read the comments line by line
|
||||
2. Read your code line by line
|
||||
3. Read the hints
|
||||
4. Explain the problem you’re having to a neighbor
|
||||
5. Press the reload button and try again
|
||||
6. Ask the teacher
|
||||
|
||||
### Written Reflection (5 mins)
|
||||
|
||||
**Tell me how you used a shortcut to save time and effort.**
|
||||
|
||||
>I used while True to make my code repeat forever. I had to remember to put four spaces on each line. It’s good because you don’t have to type all the code.
|
||||
|
||||
**What are the things you have to remember to write an infinite loop?**
|
||||
|
||||
>You have to type while True, and remember to put a : after it. On the next line, put four spaces before your code. If you want more than one line to repeat, they all have to have four spaces.
|
||||
|
||||
**Can you give me tips about solving these kinds of levels? Give an example.**
|
||||
>You have to see what are the things that repeat. Sometimes it is just one thing, and sometimes it is lots of things. For example, in the Haunted Kithmaze you go to a dead end if you just put moveRight() in the loop because it just goes right, right, right forever. You also have to do moveUp() so it goes right, up, right, up.
|
||||
|
||||
|
||||
##### Module 3
|
||||
## Variables
|
||||
|
||||
### Summary
|
||||
|
||||
These levels introduce the game mechanic of attacking. Attacks will not work unless you specify whom to attack (`hero.attack()` is wrong; `hero.attack(jeremy)` is correct.)
|
||||
|
||||
Some of these puzzles can be hard for some students to wrap their heads around. Make sure they read the instructions thoroughly and understand the goal of each level. The challenge depends on not knowing the names of the objects you want to manipulate. Think of variables like nicknames for referring to objects when you don’t know what else to call them.
|
||||
|
||||
### Transfer Goals
|
||||
- Create a variable
|
||||
- Use a variable as an argument
|
||||
- Choose appropriate variable names
|
||||
|
||||
### Standards
|
||||
|
||||
**CCSS.Math.Practice.MP1** Make sense of problems and persevere in solving them.
|
||||
**CCSS.Math.Practice.MP2** Reason abstractly and quantitatively.
|
||||
|
||||
### Instructive Activity: Variables (10 mins)
|
||||
|
||||
#### Explain (3 mins)
|
||||
|
||||
A **variable** holds your data for later. You make a variable by giving it a name, then saying what **value** it should hold.
|
||||
|
||||
`enemy = “Kratt”`
|
||||
The variable `enemy` holds (`=`) the value `"Kratt"`
|
||||
|
||||
Now you can use your variable instead of the value itself!
|
||||
|
||||
`hero.attack(“Kratt”)` is the same as `hero.attack(enemy)`
|
||||
|
||||
So a variable can stand in for a value.
|
||||
|
||||
Variables can also be changed and checked. You could say `score = 0`, and then later `score = 1`. Or you could use your variable is in the expression for loop, i.e. `while score < 10`:
|
||||
|
||||
#### Interact (5 mins)
|
||||
|
||||
As a class, discuss your preconceptions of the word “variable.”
|
||||
|
||||
In math, it is a symbol that stands in for a number, which you are usually solving for.
|
||||
|
||||
In science, it’s a part of an experiment that can change and be observed.
|
||||
|
||||
Which aspects of coding variables are like the math kind, and which are like science?
|
||||
|
||||
#### Reflect (2 mins)
|
||||
|
||||
**How do you create a variable?** (variable = something)
|
||||
|
||||
**What can you use a variable for?** (Standing in for a value, checking it in a loop)
|
||||
|
||||
**Can you use a variable before you create it?** (No, it won’t exist yet!)
|
||||
|
||||
### Coding Time (30-45 mins)
|
||||
|
||||
Allow the students to go through the game at their own pace, keeping notes about every level on paper or digital document. We recommend using following format, which you can also print out as templates: [Progress Journal [PDF]](http://files.codecombat.com/docs/resources/ProgressJournal.pdf)
|
||||
|
||||
```
|
||||
Level #: _____ Level Name: ____________________________________
|
||||
Goal: __________________________________________________________
|
||||
What I did:
|
||||
|
||||
What I learned:
|
||||
|
||||
What was challenging:
|
||||
|
||||
|
||||
```
|
||||
|
||||
Circulate to assist. Draw students’ attention to the instructions and tips.
|
||||
|
||||
Focus on clearly communicating the goal of the level, and describing the problem they are currently facing. Remind students to read their code from start to end before asking you for help. Most problems can be solved by inserting missing quotation marks or fixing indentation.
|
||||
|
||||
### Written Reflection (5 mins)
|
||||
|
||||
**What was the hardest puzzle you solved today? How did you solve it?**
|
||||
>15 was a hard level. There were lots of enemies and I died. So I did a loop for attack, but I didn’t know the name of who to attack. So I clicked on the glasses and it said I could use `findNearestEnemy`, but it didn’t work without saying `enemy =`. Then I could `attack(enemy)` and it worked.
|
||||
|
||||
**Write a user manual for findNearestEnemy.**
|
||||
|
||||
>The hero can see which enemy is closest by writing `hero.findNearestEnemy()`. But you have to remember which one it is in a variable. You can say `enemy = hero.findNearestEnemy()`. Then you can attack the enemy on the next line by saying `hero.attack(enemy)`.
|
||||
|
||||
|
||||
##### Module 4
|
||||
## Review - Multiplayer Arena
|
||||
### Summary
|
||||
|
||||
The arena level is a reward for completing the required work. Students who have fallen behind in the levels or who have not completed their written reflections should use this time to finish. As students turn in their work, they can enter the Wakka Maul arena and attempt multiple solutions until time is called.
|
||||
|
||||
### Transfer Goals
|
||||
- Write accurate Python syntax
|
||||
- Debug Python programs
|
||||
- Refine solutions based on observations
|
||||
|
||||
### Standards
|
||||
**CCSS.Math.Practice.MP1** Make sense of problems and persevere in solving them.
|
||||
**CCSS.Math.Practice.MP2** Reason abstractly and quantitatively.
|
||||
**CCSS.Math.Practice.MP6** Attend to precision.
|
||||
|
||||
### Coding Time (40-55 mins)
|
||||
|
||||
Have students navigate to the last level, **Wakka Maul**, and complete it at their own pace.
|
||||
|
||||
#### Rankings**
|
||||
|
||||
Once students beat the default computer they will be put in for the class ranking. Red teams only fight against blue teams and there will be top rankings for each. Students will only compete against the computer and other students in your CodeCombat class (not strangers).
|
||||
|
||||
Note that the class rankings are plainly visible. If some students are intimidated by competition or being publicly ranked, give them the option of a writing exercise instead:
|
||||
|
||||
- Write a walkthrough or guide to your favorite level
|
||||
- Write a review of the game
|
||||
- Design a new level
|
||||
|
||||
#### Dividing the Class
|
||||
|
||||
Students must choose a team to join: Red or Blue. It is important to divide the class as most students will choose red. It doesn’t matter if the sides are even, but it is important that there ARE players for both sides.
|
||||
|
||||
- Divide the class into two randomly by drawing from a deck of cards.
|
||||
- Students who turn in their work early join the blue team, and latecomers play red.
|
||||
|
||||
#### Refining the Code
|
||||
|
||||
Code for Wakka Maul can be submitted more than once. Encourage your students to submit code, observe how it fares against their classmates, and then make improvements and resubmit. In addition, students who have finished the code for one team can go on to create code for the other team.
|
||||
|
||||
### Reflect (5 mins)
|
||||
|
||||
**Class discussion: How is coding a solution different from controlling a hero in real time?**
|
||||
|
||||
You have been playing a game that requires you to think about a whole plan in advance, then let the hero carry out your instructions without intervention. This differs dramatically from the traditional way of playing video games by directly controlling the hero and making decisions while the game is running. Talk about how these differences feel. Which is more fun? Which is harder? How does your strategy change? How do you deal with mistakes?
|
|
@ -1,597 +0,0 @@
|
|||
###### Last updated: 09/14/2016
|
||||
|
||||
##### Lesson Plans
|
||||
# Computer Science 2
|
||||
|
||||
### Curriculum Summary
|
||||
- Recommended Prerequisite: Introduction to Computer Science
|
||||
- 6 x 45-60 minute coding sessions
|
||||
|
||||
#### Overview
|
||||
Armed with basic knowledge of the structure and syntax of simple programs, students are ready to tackle more advanced topics. Conditionals, functions, and events, oh my! Computer Science 2 is where students move past the programming-toy stage into writing code similar to that they would use in the next major software or killer app!
|
||||
|
||||
In Computer Science 2, students will continue to learn the fundamentals, (basic syntax, arguments, strings, variables, and loops) as well as being introduced to a second level of concepts for them to master. If statements allow the student to perform different actions depending on the state of the battlefield. Functions let students organize their code into reusable pieces of logic, and once students can write basic functions, they can start writing code to handle events--which is the basis for lots of coding patterns in game development, web development, and app development.
|
||||
|
||||
|
||||
_This guide is written with Python-language classrooms in mind, but can easily be adapted for JavaScript._
|
||||
|
||||
### Scope and Sequence
|
||||
|
||||
| Module | First Level | Transfer Goals |
|
||||
| ----------------------------------------------------------- | :-------------------- | :--------------------------------- |
|
||||
| [5. Conditionals (if)](#conditionals-if-) | Defense of Plainswood | Check expression before executing |
|
||||
| [6. Conditionals (else)](#conditionals-else-) | Back to Back | Execute default code |
|
||||
| [7. Nested Conditionals](#nested-conditionals) | Forest Fire Dancing | Put one conditional inside another |
|
||||
| [8. Functions](#functions) | Village Rover | Save code for later |
|
||||
| [9. Events](#events) | Backwoods Buddy | Listen for events and execute code |
|
||||
| [10. Review - Multiplayer Arena](#review-multiplayer-arena) | Power Peak | Design and implement algorithms |
|
||||
|
||||
### Core Vocabulary
|
||||
**Object** - a character or thing that can perform actions. Objects are the building blocks of Python. They are things or characters that can perform actions. Your `hero` is an object. It can perform the moving actions. In `hero.moveRight()`, the object is `hero`. In Course 2, students will also be using the `pet` object to perform actions.
|
||||
|
||||
**Function** - an action performed by an object. Functions are actions an object can do. `moveRight()` is a function. Function names are always followed by parentheses.
|
||||
|
||||
**Argument** - additional information for a function. Arguments are what we put inside the parentheses of a function. They tell the function more information about what it should do. In `hero.attack(enemy)`, `enemy` is the argument.
|
||||
|
||||
**Loop** - code that repeats. A loop is a way of repeating code. One way of writing loops uses the keyword `while`, followed by an expression that can be evaluated as `True` or `False`.
|
||||
|
||||
**Variable** - a holder for data. A variable holds your data for later. You create a variable by giving it a name, then saying what value it should hold.
|
||||
|
||||
**Conditional** - the building block of modern programming, the conditional. It’s named as such because of its ability to check the conditions at the moment and perform different actions depending on the expression. The player is no longer able to assume there will be an enemy to attack, or if there is a gem to grab. Now, they need to check whether it exists, check if their abilities are ready, and check if an enemy is close enough to attack.
|
||||
|
||||
**Event** - an object representing something that happened. Students can write code to respond to events: when this type of event happens, run this function. This is called event handling, and it's a very useful programming pattern and an alternative to an infinite while-loop.
|
||||
|
||||
|
||||
#### Extra activities for students who finish Course 2 early:
|
||||
- Help someone else
|
||||
- Refine a multiplayer arena strategy in Power Peak
|
||||
- Write a walkthrough
|
||||
- Write a review of the game
|
||||
- Write a guide to their favorite level
|
||||
- Design a new level
|
||||
|
||||
##### Module 5
|
||||
## Conditionals (If)
|
||||
|
||||
### Summary
|
||||
|
||||
Course 2 introduces more advanced programming concepts, so the progress through the levels should be slower. Pay careful attention to the directions, so you know what the goal of the level is, and to the in-line comments (denoted with a `#`) so you know what code is missing.
|
||||
|
||||
### Transfer Goals
|
||||
- Construct a conditional
|
||||
- Choose appropriate expressions
|
||||
- Evaluate expressions
|
||||
|
||||
### Standards
|
||||
**CCSS.Math.Practice.MP1** Make sense of problems and persevere in solving them.
|
||||
**CCSS.Math.Practice.MP2** Reason abstractly and quantitatively.
|
||||
**CCSS.Math.Practice.MP4** Model with mathematics.
|
||||
**CCSS.Math.Practice.MP7** Look for and make use of structure.
|
||||
|
||||
### Instructive Activity: Conditionals (10 mins)
|
||||
#### Explain (2 mins)
|
||||
Conditionals carry out code depending on the state of the game. They start by evaluating a statement as `True` or `False`, then they carry out the code only if the statement is `True`. Notice that the syntax is similar to a loop, since it needs a colon and a four-space indent.
|
||||
|
||||
`if` is the keyword, and `==` is the expression
|
||||
``` python
|
||||
if enemy == “Kratt”:
|
||||
attack(enemy) # This is the action
|
||||
```
|
||||
|
||||
The keyword for a conditional is `if`. A conditional on its own will only happen once, but if you want it to keep checking, you have to put it inside a loop. Notice how the indentation works.
|
||||
|
||||
|
||||
``` python
|
||||
while true:
|
||||
if enemy == “Kratt”:
|
||||
attack(enemy)
|
||||
```
|
||||
|
||||
#### Interact (5 mins)
|
||||
Rewrite your classroom rules as conditionals using Python syntax.
|
||||
|
||||
Identify some school or classroom rules, and write them on the board, e.g.
|
||||
- Raise your hand to ask a question.
|
||||
- You get a detention if you’re late.
|
||||
- Stop talking when the teacher claps twice.
|
||||
|
||||
Reformulate them in English to start with the word “If”, e.g.
|
||||
- **If** you have a question, then raise your hand.
|
||||
- **If** you’re late, then you get a detention.
|
||||
- **If** the teacher claps twice, then stop talking.
|
||||
|
||||
Now reformulate again using Python syntax, e.g.
|
||||
|
||||
``` python
|
||||
if student.hasQuestion():
|
||||
student.raise(hand)
|
||||
```
|
||||
``` python
|
||||
if student.arrivalTime > class.startTime:
|
||||
teacher.giveDetention(student)
|
||||
```
|
||||
``` python
|
||||
if teacher.claps == 2:
|
||||
class.volume = 0
|
||||
```
|
||||
|
||||
Label each of the parts of the conditionals: *keyword*, *expression*, *action*.
|
||||
|
||||
#### Explain (1 min)
|
||||
Code is called code because we’re encoding our ideas into a language the computer can understand. You can use this three-step process of reformulating your ideas any time you’re writing code. As long as you know the syntax of the programming language, you know what the encoded idea should look like!
|
||||
|
||||
#### Reflect (2 mins)
|
||||
**Why do we need conditionals?** (Not all actions happen all the time)
|
||||
**What is the part that comes between the if and the colon?** (an expression)
|
||||
**What’s important about expressions?** (They have to be True or False)
|
||||
|
||||
|
||||
### Coding Time (30-45 mins)
|
||||
Allow the students to go through the game at their own pace, keeping notes about every level on paper or digital document. We recommend using following format, which you can also print out as templates: [Progress Journal [PDF]](http://files.codecombat.com/docs/resources/ProgressJournal.pdf)
|
||||
|
||||
```
|
||||
Level #: _____ Level Name: ____________________________________
|
||||
Goal: __________________________________________________________
|
||||
What I did:
|
||||
|
||||
What I learned:
|
||||
|
||||
What was challenging:
|
||||
|
||||
|
||||
```
|
||||
|
||||
Circulate to assist. Draw students’ attention to the instructions and tips. Students will need to use (x,y) coordinates to specify locations. Exact coordinates can be found by placing the mouse pointer over the target position. Students will also have to use a conditional to check if a condition is met before taking an action.
|
||||
|
||||
### Written Reflection (5 mins)
|
||||
**What does if mean? What kinds of things did you write after if?**
|
||||
>If does the code only if it’s true. You can see if all kinds of things are true, like if your weapon is ready or if the enemy is close. Sometimes you need == or >, but sometimes you only need the ().
|
||||
|
||||
**If you could design a CodeCombat level, what would it look like?**
|
||||
>There would be lots of ogres and you have to attack them, but not the humans. And you would protect the village by building walls and fires.
|
||||
|
||||
|
||||
##### Module 6
|
||||
## Conditionals (Else)
|
||||
### Summary
|
||||
|
||||
These levels have two things going on at once. Students have to decide under which condition to do each action. This is a good point to have a tips & tricks discussion, where any student who wants to share a discovery or shortcut with the class may present their advice.
|
||||
|
||||
### Transfer Goals
|
||||
- Construct an if-else conditional.
|
||||
- Identify different actions taking place in different circumstances.
|
||||
- Define `else` as the opposite of `if`.
|
||||
|
||||
### Standards
|
||||
**CCSS.Math.Practice.MP1** Make sense of problems and persevere in solving them.
|
||||
**CCSS.Math.Practice.MP2** Reason abstractly and quantitatively.
|
||||
**CCSS.Math.Practice.MP7** Look for and make use of structure.
|
||||
**CCSS.Math.Practice.MP8** Look for and express regularity in repeated reasoning.
|
||||
|
||||
### Instructive Activity: Conditionals (Else) (10 mins)
|
||||
|
||||
#### Explain (2 mins)
|
||||
We’re used to using conditionals to do something if the expression is `True`, but what if it’s `False`? That’s where `else` comes in. `else` means “if not” or “otherwise” or “the opposite”.
|
||||
|
||||
Notice that `else` must be indented the same number of spaces as the if it goes with. And it also needs a colon `:` just like `if`.
|
||||
|
||||
Below, `if` and `else` are keywords, and `==` is the expression
|
||||
``` python
|
||||
if today == weekday:
|
||||
goToSchool() # action
|
||||
else: # keyword
|
||||
watchCartoons() # action
|
||||
```
|
||||
|
||||
|
||||
#### Interact (6 mins)
|
||||
Revisit the classroom rules from the previous lesson and see if any need else statements, e.g.
|
||||
|
||||
``` python
|
||||
if student.hasQuestion():
|
||||
student.raise(hand)
|
||||
else:
|
||||
student.payAttention()
|
||||
```
|
||||
|
||||
``` python
|
||||
if student.arrivalTime > class.startTime:
|
||||
teacher.giveDetention(student)
|
||||
else:
|
||||
teacher.markPresent(student)
|
||||
```
|
||||
|
||||
``` python
|
||||
if teacher.claps == 2:
|
||||
class.volume = 0
|
||||
# this doesn’t need an else because no action is taken if the teacher doesn't clap
|
||||
```
|
||||
|
||||
Label the parts of these conditionals: _keywords_ (`if` and `else`), _expression_, _actions_
|
||||
|
||||
#### Reflect (2 mins)
|
||||
**What does else mean?** (if not)
|
||||
**Why doesn’t else come with another expression?** (the expression is implied-- it’s the opposite of the if, or when the if is False)
|
||||
**Do you always need an else?** (no, it depends on the situation)
|
||||
|
||||
### Coding Time (30-45 mins)
|
||||
Allow the students to go through the game at their own pace, keeping notes about every level on paper or digital document. We recommend using following format, which you can also print out as templates: [Progress Journal [PDF]](http://files.codecombat.com/docs/resources/ProgressJournal.pdf)
|
||||
|
||||
```
|
||||
Level #: _____ Level Name: ____________________________________
|
||||
Goal: __________________________________________________________
|
||||
What I did:
|
||||
|
||||
What I learned:
|
||||
|
||||
What was challenging:
|
||||
|
||||
|
||||
```
|
||||
|
||||
Circulate to assist. Draw students’ attention to the instructions and tips. The crux of these levels is in deciding what should happen and when it should happen. Students have to entertain multiple possibilities to figure out what the best course of action is under every condition.
|
||||
|
||||
### Written Reflection (5 mins)
|
||||
**Do you know more code now than in the beginning? What powers do you have now that you couldn’t do before?**
|
||||
>In the beginning I could just walk around. Now I can attack enemies and see who is closest. I can also put my mouse on the screen and see the coordinates of where to go. I can use if and else to do two different things. And I can use a loop to make my code repeat.
|
||||
|
||||
**What advice can you give to someone just starting out the game?**
|
||||
>Read the directions. First figure out what you want to do and then worry about the code. You have to put : after while True and if. And you have to use four spaces every time. Most of the time the level will tell you what to do in blue writing and you just have to do it. You can use your gems to buy stuff.
|
||||
|
||||
**What do you do when you’re stuck?**
|
||||
>I ask the person next to me if they have done this level. If I am ahead, I look at it some more until they catch up. Then we work on it together. Or I ask the teacher. Sometimes the answer is in the help or in the blue text.
|
||||
|
||||
##### Module 7
|
||||
## Nested Conditionals
|
||||
### Summary
|
||||
|
||||
Serious coding starts now. Students will have to remember how to construct conditionals and expressions, or refer to the tips below the code editor. These levels have three or more actions to control, so they require complex thinking and planning. Up to three levels of indentation are used, so checking spaces is vital to writing code that runs.
|
||||
|
||||
### Transfer Goals
|
||||
- Construct a nested conditional
|
||||
- Read and understand a nested conditional
|
||||
- Attend to indentation
|
||||
|
||||
### Standards
|
||||
**CCSS.Math.Practice.MP1** Make sense of problems and persevere in solving them.
|
||||
**CCSS.Math.Practice.MP2** Reason abstractly and quantitatively.
|
||||
**CCSS.Math.Practice.MP6** Attend to precision.
|
||||
**CCSS.Math.Practice.MP7** Look for and make use of structure.
|
||||
|
||||
### Instructive Activity: Nested Conditionals (10 mins)
|
||||
|
||||
#### Explain (3 mins)
|
||||
We started out with conditionals checking one thing before we took an action. Then we had two actions to do and had to decide which one to do when. But sometimes you have more than two things you want to do. That’s when you want to put a conditional inside another conditional.
|
||||
|
||||
The first conditional below is `if it's a weekend`, the second (nested) conditional is `if I have a soccer game`.
|
||||
``` python
|
||||
if it’s a weekend:
|
||||
if I have a soccer game:
|
||||
Wake up at 6
|
||||
else:
|
||||
Sleep in
|
||||
else:
|
||||
Wake up at 7
|
||||
```
|
||||
|
||||
Indentation starts to matter a lot now. We indent four spaces to put code inside a loop or conditional, and that includes other conditionals. The code inside the second conditional is indented a total of eight spaces.
|
||||
|
||||
#### Activity (5 mins)
|
||||
Have students write the rules of their wake-up times, bedtimes, or recess times as nested conditionals. Do at least three different actions, so you have to use a nested conditional.
|
||||
|
||||
When they have finished, trade papers with a partner. Read each other’s schedules and discuss them. Check for syntax and indentation.
|
||||
|
||||
Invite volunteers to share their finished schedules with the class.
|
||||
|
||||
#### Reflect (2 mins)
|
||||
**Why do we need nested conditionals?** (Because sometimes more than two different actions are possible)
|
||||
**Why do we indent the second conditional by 4 spaces?** (To show that it is inside the first conditional.)
|
||||
**What does it mean when an action is indented by 8 spaces?** (It depends on two expressions being True or False)
|
||||
|
||||
### Coding Time (30-45 mins)
|
||||
Allow the students to go through the game at their own pace, keeping notes about every level on paper or digital document. We recommend using following format, which you can also print out as templates: [Progress Journal [PDF]](http://files.codecombat.com/docs/resources/ProgressJournal.pdf)
|
||||
|
||||
```
|
||||
Level #: _____ Level Name: ____________________________________
|
||||
Goal: __________________________________________________________
|
||||
What I did:
|
||||
|
||||
What I learned:
|
||||
|
||||
What was challenging:
|
||||
|
||||
|
||||
```
|
||||
|
||||
Circulate to assist. Make sure students are reading all the comments in the starter code before they start making changes. The goals are complex, so understanding each sub-goal is important. Encourage collaboration and allow students to help each other.
|
||||
|
||||
### Written Reflection (5 mins)
|
||||
**Tell me about cleave.**
|
||||
>Cleave smashes a bunch of enemies all around you. You do it by saying hero.cleave(). You have to put (), but you don’t need to say cleave(enemy). It just does it to everyone. Cleave takes a while to warm up, so you can check if it’s ready with the watch, and if it’s not ready yet just do a normal attack.
|
||||
|
||||
**Debate: Is your hero a good guy or a bad guy?**
|
||||
>My hero is sort of a good guy and sort of a bad guy. He is a good guy because he protects the villagers from getting hurt. But he is a bad guy because he stole the gems from the ogres in the dungeon. And he kills people. Maybe he should protect the people without killing and not steal.
|
||||
|
||||
### Writing Checkpoint: Conditionals
|
||||
|
||||
**What is a conditional? How many different ways can you write a conditional? Give an example.**
|
||||
>A conditional asks “if.” You can say if something is true, then do something. You can use else if you want to do something if that first thing was not true. Elif is for if you want to do three things, like if it’s raining wear a jacket elif it’s snowing wear a hat else wear a t-shirt. You can put ifs inside other ifs but you have to remember the right number of spaces.
|
||||
|
||||
**What is elif? Is it an elf?**
|
||||
>Elif means else if. You use it to do three things instead of two with if. It’s like an elf because it’s tricky.
|
||||
|
||||
**Tell me about spaces.**
|
||||
>You use four spaces to make code go inside a while True, if, else, or elif. If an if is inside another if, you have to use eight spaces. It’s important to count the spaces and get them exactly right, or else the computer thinks you mean something different. You have to be really careful.
|
||||
|
||||
|
||||
##### Module 8
|
||||
## Functions
|
||||
### Summary
|
||||
These levels give students the chance to take some shortcuts. Just like loops gave them the power to write more code quickly, functions enable reuse of code. Syntax remains vital; so check that colons and indentation are in the right place, and remember to read and understand the directions for each level before starting to code a solution.
|
||||
|
||||
### Transfer Goals
|
||||
- Identify functions.
|
||||
- Construct a function definition.
|
||||
- Call a function.
|
||||
|
||||
### Standards
|
||||
**CCSS.Math.Practice.MP1** Make sense of problems and persevere in solving them.
|
||||
**CCSS.Math.Practice.MP2** Reason abstractly and quantitatively.
|
||||
**CCSS.Math.Practice.MP7** Look for and make use of structure.
|
||||
|
||||
### Instructive Activity: Functions (10 mins)
|
||||
#### Explain
|
||||
You’ve been using functions already! When you type `hero.cleave()`, `cleave()` is a function. So far you’ve only been using built-in functions, but you can also write your own. First, you need to define the function using `def`.
|
||||
|
||||
``` python
|
||||
def getReady():
|
||||
hero.wash(face)
|
||||
hero.brush(teeth)
|
||||
hero.putOn(armor)
|
||||
```
|
||||
|
||||
Then you need to call the function.
|
||||
|
||||
``` python
|
||||
getReady()
|
||||
```
|
||||
|
||||
**What is the difference between defining and calling?** (Defining needs def before, and a colon after. Then it has some code indented under it. They both have parentheses.)
|
||||
|
||||
Programmers use functions to make their code easy to read and quick to write. It’s sort of like a set play in basketball: you know how to shoot, dribble, and pass, so you can make up a function that combines those parts and give it a name.
|
||||
|
||||
``` python
|
||||
def out-over-up():
|
||||
p1.dribble()
|
||||
p1.pass(p2)
|
||||
p2.shoot()
|
||||
```
|
||||
|
||||
Then when the coach wants this sequence of actions to happen, she just calls out the name of the play: “Out-over-up!”
|
||||
|
||||
### Interact (5 mins)
|
||||
**Simon Says.**
|
||||
|
||||
As a class, write your own functions for complicated Simon Says moves on the board using Python syntax. Here are some examples to get you started:
|
||||
|
||||
``` python
|
||||
def pogo():
|
||||
student.handsOn(hips)
|
||||
student.jump()
|
||||
```
|
||||
|
||||
``` python
|
||||
def popcorn():
|
||||
if student.sittingDown():
|
||||
student.standUp()
|
||||
else:
|
||||
student.sitDown()
|
||||
```
|
||||
|
||||
Then, play Simon Says by calling the functions, e.g.
|
||||
- Simon says raise your hand!
|
||||
- Simon says popcorn!
|
||||
- Pogo! (Simon didn’t say)
|
||||
|
||||
### Reflect (2 mins)
|
||||
**Why do functions make coding easier?** (Because you don’t have to say the complicated steps every time; you can just use the function name.)
|
||||
**Why is it important to give your functions good names?** (So you can remember what they’re for later.)
|
||||
**What does the keyword def stand for?** (define, or make)
|
||||
|
||||
### Coding Time (30-45 mins)
|
||||
Allow the students to go through the game at their own pace, keeping notes about every level on paper or digital document. We recommend using following format, which you can also print out as templates: [Progress Journal [PDF]](http://files.codecombat.com/docs/resources/ProgressJournal.pdf)
|
||||
|
||||
```
|
||||
Level #: _____ Level Name: ____________________________________
|
||||
Goal: __________________________________________________________
|
||||
What I did:
|
||||
|
||||
What I learned:
|
||||
|
||||
What was challenging:
|
||||
|
||||
|
||||
```
|
||||
|
||||
Circulate to assist. Draw students’ attention to the instructions and tips.
|
||||
These levels are all about writing good code. The helper code that is given to you may have the word `pass` in it. This is just so the sample code doesn’t show any errors. Once the students fill in their code, they should delete `pass`. When you help debug their code, look for `pass` first.
|
||||
|
||||
### Written Reflection (5 mins)
|
||||
|
||||
**Why are functions useful? When would they not be useful?**
|
||||
>They make it so you don’t have to write the same code over and over and they make your code easier to read. I don’t think it’s useful if you’re just going to put one line of code in your function. It would be easier just to write that one line every time.
|
||||
|
||||
|
||||
##### Module 9
|
||||
## Events
|
||||
### Summary
|
||||
An **event** is an object representing something that happened. Students can write code to respond to events: when this type of event happens, run this function. This is called event handling, and it's a very useful programming pattern and an alternative to an infinite while-loop.
|
||||
|
||||
### Transfer Goals
|
||||
- Listen for events and execute code
|
||||
- Use event handling to control a pet
|
||||
- Write concurrent code mixing direct execution and event handling
|
||||
|
||||
### Instructive Activity: President Teacher (12 mins)
|
||||
#### Explain (2 mins)
|
||||
Up until now, you have been writing code that executes once, from top to bottom: *first do this, then do this, then do that*. You also learned how to write while loops, where you can say, *then do this forever*. Using event handling, you now have a way to say, * **when** this happens, **then** do that*. It's kind of like an if-statement, except events can happen at any time, not just when you are checking for them.
|
||||
|
||||
#### Interact (8 mins)
|
||||
Explain to the class that you're waiting for an important call from the White House about whether you've been elected the next President. You're going to write a program to answer the phone when it rings using a while loop and an if statement, but with no events yet:
|
||||
|
||||
``` python
|
||||
while True:
|
||||
if phone.isRinging:
|
||||
teacher.answer(phone)
|
||||
```
|
||||
|
||||
But that's boring, since you're not doing anything else. So you're going to grade their homework while you wait:
|
||||
|
||||
``` python
|
||||
while True:
|
||||
paper = teacher.findNextPaper()
|
||||
teacher.grade(paper)
|
||||
if phone.isRinging:
|
||||
teacher.answer(phone)
|
||||
```
|
||||
|
||||
|
||||
Say that each paper takes five minutes to grade. Ask the class what will likely happen if you are running this program and you get a phone call from the White House. (You will probably be in the middle of grading the paper and will only check if the phone is ringing every five minutes, thus you'll probably miss the call and won't get to be President.)
|
||||
|
||||
Now rewrite the program to use event handling, explaining how you **listen** for events so that when they happen, you can **handle** them by running a function:
|
||||
|
||||
``` python
|
||||
def answerPhone():
|
||||
teacher.answer(phone)
|
||||
|
||||
phone.on("ring", answerPhone)
|
||||
```
|
||||
|
||||
Explain that you pronounce this as, "On the `phone`'s `"ring"` event, run the `answerPhone` function." Now say you want to grade papers while you wait, you just add a while loop, and when the event happens, it will interrupt your grading so you can answer the phone and become President:
|
||||
|
||||
``` python
|
||||
def answerPhone():
|
||||
teacher.answer(phone)
|
||||
|
||||
phone.on("ring", answerPhone)
|
||||
while True:
|
||||
paper = teacher.findNextPaper()
|
||||
teacher.grade(paper)
|
||||
```
|
||||
|
||||
Explain that the `phone.on("ring", answerPhone)` makes your code start listening for the `"ring"` event, and note that you **don't use parentheses** on the function you are listening with: `answerPhone`, not `answerPhone()`. This is because you are telling the code the name of the function to run, but you are **not running it yet**. (The parentheses would run the function immediately.)
|
||||
|
||||
Ask the class for more examples of events and functions that could respond to them, and write them on the board, something like this:
|
||||
|
||||
``` python
|
||||
student.on("wake", goBackToSleep)
|
||||
dog.on("hear", obeyMaster)
|
||||
goal.on("touchBall", increaseScore)
|
||||
bigRedButton.on("press", initiateSelfDestruct)
|
||||
```
|
||||
|
||||
|
||||
#### Reflect (2 mins)
|
||||
**What do you use event handling for?** (To run a function when something happens.)
|
||||
**What kind of data is an event name?** (The event name you listen to is a string.)
|
||||
**Why don't you use function parentheses when you start listening to an event?** (The parentheses would make the function run now, and you want to run it later when the event happens.)
|
||||
|
||||
### Coding Time (30-45 mins)
|
||||
Allow the students to go through the game at their own pace, keeping notes about every level on paper or digital document. We recommend using following format, which you can also print out as templates: [Progress Journal [PDF]](http://files.codecombat.com/docs/resources/ProgressJournal.pdf)
|
||||
|
||||
```
|
||||
Level #: _____ Level Name: ____________________________________
|
||||
Goal: __________________________________________________________
|
||||
What I did:
|
||||
|
||||
What I learned:
|
||||
|
||||
What was challenging:
|
||||
|
||||
|
||||
```
|
||||
|
||||
Circulate to assist. Draw students’ attention to the instructions and tips. Make sure students are writing their functions above where they use them to start listening to events. It can be trick to keep track of what code is executing when events happen (your default program or the event handler function), so have students look at the white code execution highlights to see what line of code is being run at each time.
|
||||
|
||||
|
||||
### Written Reflection (5 mins)
|
||||
Select appropriate prompt(s) for the students respond to, referring to their notes.
|
||||
|
||||
**Tell me about the cat.**
|
||||
>I got a pet cat and it’s a cougar or a lioness. There was a function that said meow, and the cat waited until you talked to it and then it said meow. I think the cat should help protect you from enemies. You should be able to make it do other stuff by commands, like pouncing and biting.
|
||||
|
||||
**Events are really useful when developing games. Guess the names of at least three kinds of events you think might happen in code for games you like to play.**p
|
||||
>In Minecraft there might be an "explosion" event when a creeper blows up. In chess there might be a "checkmate" event. In Bejeweled there could be a "combo" event.
|
||||
|
||||
|
||||
##### Module 10
|
||||
## Review - Multiplayer Arena
|
||||
### Summary
|
||||
|
||||
The arena level is a reward for completing the required work. Students who have fallen behind in the levels or who have not completed their written reflections should use this time to finish. As students turn in their work, they can enter the Power Peak arena and attempt multiple solutions until time is called.
|
||||
|
||||
### Transfer Goals
|
||||
- Design an algorithm to solve a problem.
|
||||
- Implement an algorithm in Python.
|
||||
- Debug a Python program.
|
||||
|
||||
### Standards
|
||||
**CCSS.Math.Practice.MP1** Make sense of problems and persevere in solving them.
|
||||
**CCSS.Math.Practice.MP2** Reason abstractly and quantitatively.
|
||||
**CCSS.Math.Practice.MP3** Construct viable arguments and critique the reasoning of others.
|
||||
**CCSS.Math.Practice.MP5** Use appropriate tools strategically.
|
||||
**CCSS.Math.Practice.MP6** Attend to precision.
|
||||
|
||||
### Instructive Activity: Engineering Cycle (10 mins)
|
||||
#### Explain (3 mins)
|
||||
Engineering is all about solving problems, but the first rule of engineering is that no one gets it right the first time. That’s where the Engineering Cycle comes in:
|
||||
|
||||
First, we DESIGN a solution to our problem. This includes figuring out what the problem is, and breaking it down into smaller parts. Then we IMPLEMENT this design, which putting our ideas into action with code. Third, we TEST our implementation. Does it work? Does it solve the problem? If our test fails, we have to decide if it was because of the DESIGN or the IMPLEMENTATION.
|
||||
|
||||
Then we keep designing, implementing, and testing until it the problem is solved!
|
||||
|
||||
#### Reflect (2 mins)
|
||||
**What are the steps of the Engineering Cycle?** (Design, implement, test)
|
||||
**When does the Engineering Cycle stop?** (When the problem is solved, or you run out of time)
|
||||
|
||||
#### Interact (5 mins)
|
||||
As a class, make a list of all the things your hero can do (functions). Use appropriate vocabulary. Annotate with any tips or code snippets the students deem useful.
|
||||
`moveUp()`, `moveDown()`, `moveLeft()`, `moveRight()`
|
||||
`moveToXY(x,y)`
|
||||
`attack(something)`
|
||||
|
||||
### Coding Time (30-45 mins)
|
||||
|
||||
Have students navigate to the last level, **Power Peak**, and complete it at their own pace.
|
||||
|
||||
#### Rankings
|
||||
|
||||
Once students beat the default computer they will be put in for the class ranking. Red teams only fight against blue teams and there will be top rankings for each. Students will only compete against the computer and other students in your CodeCombat class (not strangers).
|
||||
|
||||
Note that the class rankings are plainly visible. If some students are intimidated by competition or being publicly ranked, give them the option of a writing exercise instead:
|
||||
|
||||
- Write a walkthrough or guide to your favorite level
|
||||
- Write a review of the game
|
||||
- Design a new level
|
||||
|
||||
#### Dividing the Class
|
||||
|
||||
Students must choose a team to join: Red or Blue. It is important to divide the class as most students will choose red. It doesn’t matter if the sides are even, but it is important that there ARE players for both sides.
|
||||
|
||||
- Divide the class into two randomly by drawing from a deck of cards.
|
||||
- Students who turn in their work early join the blue team, and latecomers play red.
|
||||
|
||||
#### Refining the Code
|
||||
|
||||
Code for Power Peak can be submitted more than once. Encourage your students to submit code, observe how it fares against their classmates, and then make improvements and resubmit. In addition, students who have finished the code for one team can go on to create code for the other team.
|
||||
|
||||
Remind students to use the Engineering Cycle when working on their algorithms:
|
||||
|
||||
**DESIGN**: Make observations about the level. Make a list of requirements. Decide what part of the problem you will start with.
|
||||
**IMPLEMENT**: Write the solution to that part of your problem in code. Tip: Use a different function to solve each part of the problem!
|
||||
**TEST**: Does your code work? If not, fix your code. If it does, does it solve the right part of the problem? If not, redesign. If so, move on to the next part!
|
||||
|
||||
### Written Reflection (5 mins)
|
||||
|
||||
**Writing Checkpoint: What is code?**
|
||||
>Code is when you type instructions to make the computer do things. Sometimes it gives you hints and completes the words for you. You have to spell everything right and indent the right number of spaces. Sometimes the puzzles are easy and sometimes they are hard. You have to make a plan for how to solve it, and then write the code exactly to make it work. The language we used is called Python. It has while True: to make your code repeat and if, else, and elif to make different things happen at different times.
|
|
@ -1,151 +0,0 @@
|
|||
##### Getting Started
|
||||
# Start Using CodeCombat in 10 Minutes!
|
||||
#### Get your class up and running with these steps.
|
||||
|
||||
##### STEP 1
|
||||
## Create a Teacher Account
|
||||
|
||||
After you’ve created your Teacher account, you will be able to create classes, invite students, monitor students’ progress, enroll students, and assign course content once students have been enrolled.
|
||||
|
||||
Select the **Sign up as a Teacher** option during account creation in order to sign up as a teacher.
|
||||
|
||||
<img src="http://files.codecombat.com/docs/getting-started/teacher-account.png" title="Create a Teacher Account" alt="create-teacher-account-modal"/>
|
||||
|
||||
Once your teacher account is setup, you’ll be able to access your [Teacher Dashboard](/teachers/classes).
|
||||
|
||||
### What if I already have an account?
|
||||
|
||||
If you already have a CodeCombat account as a Student or Individual but want to convert it to a Teacher account, visit the [Update to Teacher Account](/teachers/update-account) page. Once you’ve converted, your student account will be removed from any classrooms you may have previously joined.
|
||||
|
||||
### What are the technical requirements for CodeCombat?
|
||||
CodeCombat runs best on computers with at least 4GB of RAM, on a modern browser such as Chrome, Safari, Firefox, or Edge. Chromebooks with less RAM may have minor graphics issues in later courses.
|
||||
|
||||
*We do not currently support iPads or Android Tablets at this time.*
|
||||
|
||||
##### STEP 2
|
||||
## Create a New Class
|
||||
|
||||
Once logged in, or if you click the Teacher link in the navigation bar, you’ll see your new [Teacher Dashboard](/teachers/classes). From here, you’ll be able to create classes and monitor your student’s progress.
|
||||
|
||||
Click the blue “Create a New Class” button, then choose a class name that will help you and your students identify the class, such as “Mr. Smith 3rd period.”
|
||||
|
||||
<img src="http://files.codecombat.com/docs/getting-started/create-new-class-modal.png" title="Create a New Class" alt="create-new-class-modal" width="400px" />
|
||||
|
||||
|
||||
|
||||
### Should I choose Python or JavaScript?
|
||||
We recommend Python, because it’s both beginner-friendly and currently used by major corporations (such as Google). If you have younger or first-time learners, we strongly recommend Python.
|
||||
|
||||
JavaScript will work great too. It’s the language of the web; used across every website, and still beginner-friendly. If you are planning to also study web development, you may prefer to choose JavaScript to avoid the confusion some students may have switching from one language’s syntax to another. However, JavaScript’s syntax is a little more difficult for beginners than Python.
|
||||
|
||||
##### STEP 3
|
||||
## Add Students
|
||||
Once you’ve created your class, you’ll see it under the list of Current Classes. Navigate the class where you want to add students, then choose one of three ways to add students.
|
||||
|
||||
<img src="http://files.codecombat.com/docs/getting-started/add-students.png" title="Add Students" alt="add-students" width="400px"/>
|
||||
_Use your unique class code, class URL or invite students via email._
|
||||
|
||||
|
||||
### Option 1: Invite Students via Email
|
||||
*Easiest option if your students have email addresses*
|
||||
Click the blue "Invite Students by Email" button, then enter your students’ email addresses (you can copy and paste this from your class list or student information system) and click "Invite Students". Students will receive an email instructing them to follow a link, which will allow them to create an account and join your class.
|
||||
|
||||
Make sure they are creating a **Student Account** and that the correct class name is displayed when they create their account.
|
||||
|
||||
<img src="http://files.codecombat.com/docs/getting-started/test-class.png" title="Create a Student Account" alt="create-student-account" width="400px"/>
|
||||
_Students should see your class name in place of "Test Class 1"._
|
||||
|
||||
Students will need to enter the following to create a Student Account:
|
||||
- First name
|
||||
- Last initial
|
||||
- Username (help them choose an appropriate unique username)
|
||||
- Password
|
||||
|
||||
Email addresses are _not required_ for students creating an account when they have a valid Class Code from you. That said, using an email address is recommended if they have one, for easier password recovery.
|
||||
|
||||
*If your school uses Google Apps for Education*, students can connect using the red "Google" button at the top of the screen instead of manually entering their information. They can then sign in to CodeCombat using the G+ Connect button in the future.
|
||||
|
||||
After students have created their account, they are shown their username and instructed to write down this information.
|
||||
|
||||
That’s it! Students can now use their login credentials to start playing CodeCombat!
|
||||
|
||||
### Option 2: Students Join via a Class Code
|
||||
*Easiest option if you students don’t have email addresses*
|
||||
|
||||
Direct your students to [CodeCombat](https://codecombat.com) and have them click “Create Account” on the top navigation bar. Students should select the green "Sign up as a Student" button.
|
||||
|
||||
<img src="http://files.codecombat.com/docs/getting-started/student-account.png" title="Create a Student Account" alt="create-student-account" width="400px"/>
|
||||
_Make sure your students click “Sign up as a Student” when prompted._
|
||||
|
||||
The website will request the “Class Code” for your class, which can be found if you click “View Class” on your Teacher Dashboard. Your classroom will have its own unique three-word code.
|
||||
|
||||
<img src="http://files.codecombat.com/docs/getting-started/class-code-example.png" title="Class Code Example" alt="class-code-example" width="400px"/>
|
||||
_How to find your Class Code as a Teacher: Click on one of your classes, and look for the three-word Class Code next under “Adding Students”._
|
||||
|
||||
Once students enter your Class Code, they should see the correct class name and instructor on the screen.
|
||||
|
||||
Students will need to enter the following to create a Student Account:
|
||||
- First name
|
||||
- Last initial
|
||||
- Username (help them choose an appropriate unique username)
|
||||
- Password
|
||||
|
||||
Email addresses are _not required_ for students creating an account when they have a valid Class Code from you. That said, using an email address is recommended if they have one, for easier password recovery.
|
||||
|
||||
*If your school uses Google Apps for Education*, students can connect using the red "Google" button at the top of the screen instead of manually entering their information. They can then sign in to CodeCombat using the G+ Connect button in the future.
|
||||
|
||||
After students have created their account, they are shown their username and instructed to write down this information.
|
||||
|
||||
That’s it! Students can now use their login credentials to start playing CodeCombat!
|
||||
|
||||
##### STEP 4
|
||||
## Introduction to Computer Science
|
||||
|
||||
All students are automatically granted access to the first course in CodeCombat, Introduction to Computer Science. This is a course that introduces students to concepts such as basic syntax, variables, and while loops. Generally this course takes about 1-3 hours for a middle school class.
|
||||
|
||||
<img src="http://files.codecombat.com/docs/getting-started/course-guides.png" title="Course Guides" alt="course-guides" width="400px"/>
|
||||
_Course Guides allow you to preview course levels and view solutions._
|
||||
|
||||
As a teacher, you can access solutions for each course by going to [Course Guides](/teachers/courses/) (located in the blue teacher navigation bar). You can also preview every level using the dropdown selectors.
|
||||
|
||||
<img src="http://files.codecombat.com/docs/getting-started/resource-hub.png" title="Resource Hub" alt="resource-hub" width="400px"/>
|
||||
_Course Guides allow you to preview course levels and view solutions._
|
||||
|
||||
If you're new to teaching computer science, we also recommend taking a look at the [Resource Hub](/teachers/resources), where you can find lesson plans, worksheets and supplemental guides to help you kickstart your classroom.
|
||||
|
||||
##### STEP 5
|
||||
## Tracking Progress
|
||||
|
||||
<img src="http://files.codecombat.com/docs/getting-started/student-overview.png" title="Student Overview" alt="student-overview" width="400px"/>
|
||||
_A high-level view of student progress in each course is displayed in the main class view._
|
||||
|
||||
After students join the class, you’ll see their progress appear in the individual classroom pages. Any assigned courses and each student’s progress in each course (starting with CS1, Introduction to Computer Science and onwards) is represented by a colored circle. A grey circle means a student has not begun any levels in that course, yellow circle means they have started working on the course’s levels, and a green circle means that they’ve completed all of the levels in the course.
|
||||
|
||||
<img src="http://files.codecombat.com/docs/getting-started/student-progress.png" title="Student Progress" alt="student-progress" width="400px"/>
|
||||
_Navigate to the "Course Progress" tab to view more detailed information of student progress within each course._
|
||||
|
||||
If you want to see how your students are doing within a course, click on the “Course Progress” tab. You’ll be able to view how much progress a student has made in a specific course. A gray circle means a level has not been started, a yellow circle indicates a level has been started but not completed, and a green circle means a level has been completed. By moving your mouse pointer over the circle for a level, you can see information about when they completed the level, as well as a rough estimate of how long the level took to complete.
|
||||
|
||||
##### STEP 6
|
||||
## Licensing Students
|
||||
|
||||
Students are required to have a license to access any content after the first course. When you assign a new course, a license will automatically be applied to the student. By default, all licenses expire one year from when they are granted. A single license allows a single student access to all of the courses available.
|
||||
|
||||
To manually assign a license to a student, click on the *License Status* tab while viewing a class and use the "Assign License" button.
|
||||
|
||||
### How do I get Licenses for my students?
|
||||
|
||||
If you would like to purchase more licenses, click on *Student Licenses* in the Teacher Dashboard navigation bar, and follow the instructions under "Get More Licenses". One of CodeCombat’s specialists will be in contact with you shortly to discuss your needs.
|
||||
|
||||
##### STEP 7
|
||||
## Assigning Courses
|
||||
|
||||
<img src="http://files.codecombat.com/docs/getting-started/bulk-assign.png" title="Bulk Assign Courses" alt="bulk-assign" width="400px"/>
|
||||
|
||||
Once a student is enrolled, you’ll be able to assign additional courses to them. We recommend not assigning students to more than one course ahead of where they currently are. You can bulk-assign a course to multiple students at a time by selecting students using checkboxes on the left-hand side (or the “Select All” checkbox), then choosing the appropriate course from the dropdown menu, and then clicking “Assign to Selected Students.”
|
||||
|
||||
##### STEP 8
|
||||
## Start Teaching!
|
||||
There are great supplemental materials for teachers available on our [Course Guides](/teachers/courses/) and [Resource Hub](/teachers/resources). If you're new to teaching computer science, we highly recommend checking these out -- we've built these with first-time teachers in mind. You can also browse our [Teacher Forums](https://discourse.codecombat.com/c/teachers), where you can discuss curriculum planning with other educators, share ideas, or ask questions.
|
||||
|
||||
You can also email us at [schools@codecombat.com](mailto:schools@codecombat.com) with any support questions or concerns!
|
|
@ -1,32 +0,0 @@
|
|||
##### Activity
|
||||
# Pair Programming
|
||||
|
||||
### What is Pair Programming?
|
||||
Pair programming is an exercise that real-life engineers use to collaborate on code together. One person is the **driver**, who controls the keyboard/mouse, and the other person is the **navigator**, who observes and plans. Pair programming is great because it teaches the value of communication while allowing two brains to work on the same problem at the same time. It also reduces bugs, encourages communication and listening skills, and reinforces existing concept mastery by allowing students to fill in each others' knowledge gaps.
|
||||
|
||||
*A great time to introduce Pair Programming is during an early challenging level like Haunted Kithmaze in Course 1. You can also suggest Pair Programming during later levels where there may be a larger mastery gap -- allow a student who has a firm grasp of the concept to be the Navigator.*
|
||||
|
||||
### What you need
|
||||
- One workstation (computer, keyboard, mouse)
|
||||
- One student to be the Driver
|
||||
- One student to be the Navigator
|
||||
|
||||
|
||||
### How to set up Pair Programming
|
||||
1. Pair students up and assign one of them to be the **driver**, the other to be the **navigator**.
|
||||
2. Remind students of each role’s instructions: the **navigator** should not touch the keyboard/mouse, and the **driver** should communicate what they are doing.
|
||||
3. Every 15 minutes, switch roles. If one player is much more experienced (or, say, has done all these levels before and is helping out), you can keep them as the navigator instead of switching them into the driver role.
|
||||
4. Students should practice clear communication, patience and the spirit of collaboration.
|
||||
|
||||
### Discussion Questions
|
||||
**Was it easier to be a Driver or a Navigator? Why?**
|
||||
|
||||
**What was challenging about pair programming?**
|
||||
|
||||
**What was useful about pair programming?**
|
||||
|
||||
**When would it be beneficial to pair program with a partner?**
|
||||
|
||||
|
||||
### Additional Resources
|
||||
[NCWIT: Pair Programming Activities](https://www.ncwit.org/resources/pair-programming-box-power-collaborative-learning)
|
|
@ -1,50 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
|
||||
|
||||
<title>My CodeCombat Website</title>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/lodash/4.13.1/lodash.min.js" integrity="sha256-8SeyqJ7ZAZx8WnIgP/bgK6LGIjKjhojNPHSMV/fo29Y=" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="/javascripts/web-dev-listener.js"></script>
|
||||
|
||||
<script src="/javascripts/app/vendor/aether-html.js"></script>
|
||||
|
||||
<style>
|
||||
@import 'https://fonts.googleapis.com/css?family=Holtwood+One+SC';
|
||||
/* Import that font for demoing web-dev levels until @import ordering bug is fixed */
|
||||
|
||||
* {
|
||||
transition: 1s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
|
||||
|
||||
<!-- Optional theme -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
|
||||
|
||||
<!-- Latest compiled and minified JavaScript -->
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- Extracted player/level styles and scripts -->
|
||||
<style for="level-styles">
|
||||
</style>
|
||||
<script for="level-scripts">
|
||||
</script>
|
||||
<style for="player-styles">
|
||||
</style>
|
||||
<script for="player-scripts">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Loading...</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -15,8 +15,3 @@ module.exports = class Classrooms extends CocoCollection
|
|||
options.data ?= {}
|
||||
options.data.ownerID = me.id
|
||||
@fetch(options)
|
||||
|
||||
fetchByOwner: (ownerID, options={}) ->
|
||||
options.data ?= {}
|
||||
options.data.ownerID = ownerID
|
||||
@fetch(options)
|
||||
|
|
|
@ -33,6 +33,4 @@ module.exports = class CocoCollection extends Backbone.Collection
|
|||
|
||||
setProjection: (@project) ->
|
||||
|
||||
stringify: -> return JSON.stringify(@toJSON())
|
||||
|
||||
wait: (event) -> new Promise((resolve) => @once(event, resolve))
|
||||
stringify: -> return JSON.stringify(@toJSON())
|
|
@ -4,8 +4,3 @@ CocoCollection = require 'collections/CocoCollection'
|
|||
module.exports = class Courses extends CocoCollection
|
||||
model: Course
|
||||
url: '/db/course'
|
||||
|
||||
fetchReleased: (options = {}) ->
|
||||
options.data ?= {}
|
||||
options.data.releasePhase = 'released'
|
||||
@fetch(options)
|
||||
|
|
|
@ -3,9 +3,3 @@ CocoCollection = require 'collections/CocoCollection'
|
|||
|
||||
module.exports = class Patches extends CocoCollection
|
||||
model: PatchModel
|
||||
|
||||
fetchMineFor: (targetModel, options={}) ->
|
||||
options.url = "#{_.result(targetModel, 'url')}/patches"
|
||||
options.data ?= {}
|
||||
options.data.creator = me.id
|
||||
@fetch(options)
|
||||
|
|
6
app/collections/RealTimeCollection.coffee
Normal file
|
@ -0,0 +1,6 @@
|
|||
module.exports = class RealTimeCollection extends Backbone.Firebase.Collection
|
||||
constructor: (savePath) ->
|
||||
# TODO: Don't hard code this here
|
||||
# TODO: Use prod path in prod
|
||||
@firebase = 'https://codecombat.firebaseio.com/test/db/' + savePath
|
||||
super()
|
|
@ -84,5 +84,3 @@ module.exports = class CocoClass
|
|||
|
||||
playSound: (trigger, volume=1) ->
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: trigger, volume: volume
|
||||
|
||||
wait: (event) -> new Promise((resolve) => @once(event, resolve))
|
||||
|
|
|
@ -18,9 +18,6 @@ class NameLoader extends CocoClass
|
|||
loadedNames: (newNames) =>
|
||||
_.extend namesCache, newNames
|
||||
|
||||
getName: (id) ->
|
||||
if namesCache[id]?.firstName and namesCache[id]?.lastName
|
||||
return "#{namesCache[id]?.firstName} #{namesCache[id]?.lastName}"
|
||||
namesCache[id]?.firstName or namesCache[id]?.name or id
|
||||
getName: (id) -> namesCache[id]?.name or id
|
||||
|
||||
module.exports = new NameLoader()
|
||||
|
|
|
@ -84,6 +84,7 @@ module.exports = ParticleMan = class ParticleMan extends CocoClass
|
|||
|
||||
addEmitter: (x, y, kind="level-dungeon-premium") ->
|
||||
return if @unsupported
|
||||
kind = kind.replace 'intro', 'dungeon'
|
||||
options = $.extend true, {}, particleKinds[kind]
|
||||
return console.error "Couldn't find particle configuration for", kind unless options.group
|
||||
options.group.texture = THREE.ImageUtils.loadTexture "/images/common/particles/#{options.group.texture}.png"
|
||||
|
@ -244,12 +245,6 @@ particleKinds['level-dungeon-game-dev'] = particleKinds['level-dungeon-game-dev-
|
|||
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||
colorEnd: hsl 0.7, 0.75, 0.3
|
||||
|
||||
particleKinds['level-dungeon-web-dev'] = particleKinds['level-dungeon-web-dev-premium'] = ext particleKinds['level-dungeon-hero-ladder'],
|
||||
emitter:
|
||||
colorStart: hsl 0.7, 0.25, 0.7
|
||||
colorMiddle: hsl 0.7, 0.25, 0.5
|
||||
colorEnd: hsl 0.7, 0.25, 0.3
|
||||
|
||||
particleKinds['level-dungeon-premium-item'] = ext particleKinds['level-dungeon-gate'],
|
||||
emitter:
|
||||
particleCount: 2000
|
||||
|
@ -305,12 +300,6 @@ particleKinds['level-forest-game-dev'] = particleKinds['level-forest-game-dev-pr
|
|||
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||
colorEnd: hsl 0.7, 0.75, 0.3
|
||||
|
||||
particleKinds['level-forest-web-dev'] = particleKinds['level-forest-web-dev-premium'] = ext particleKinds['level-forest-hero-ladder'],
|
||||
emitter:
|
||||
colorStart: hsl 0.7, 0.25, 0.7
|
||||
colorMiddle: hsl 0.7, 0.25, 0.5
|
||||
colorEnd: hsl 0.7, 0.25, 0.3
|
||||
|
||||
particleKinds['level-forest-premium-item'] = ext particleKinds['level-forest-gate'],
|
||||
emitter:
|
||||
particleCount: 2000
|
||||
|
@ -366,12 +355,6 @@ particleKinds['level-desert-game-dev'] = particleKinds['level-desert-game-dev-pr
|
|||
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||
colorEnd: hsl 0.7, 0.75, 0.3
|
||||
|
||||
particleKinds['level-desert-web-dev'] = particleKinds['level-desert-web-dev-premium'] = ext particleKinds['level-desert-hero-ladder'],
|
||||
emitter:
|
||||
colorStart: hsl 0.7, 0.25, 0.7
|
||||
colorMiddle: hsl 0.7, 0.25, 0.5
|
||||
colorEnd: hsl 0.7, 0.25, 0.3
|
||||
|
||||
particleKinds['level-mountain-premium-hero'] = ext particleKinds['level-mountain-premium'],
|
||||
emitter:
|
||||
particleCount: 200
|
||||
|
@ -412,12 +395,6 @@ particleKinds['level-mountain-game-dev'] = particleKinds['level-mountain-game-de
|
|||
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||
colorEnd: hsl 0.7, 0.75, 0.3
|
||||
|
||||
particleKinds['level-mountain-web-dev'] = particleKinds['level-mountain-web-dev-premium'] = ext particleKinds['level-mountain-hero-ladder'],
|
||||
emitter:
|
||||
colorStart: hsl 0.7, 0.25, 0.7
|
||||
colorMiddle: hsl 0.7, 0.25, 0.5
|
||||
colorEnd: hsl 0.7, 0.25, 0.3
|
||||
|
||||
particleKinds['level-glacier-premium-hero'] = ext particleKinds['level-glacier-premium'],
|
||||
emitter:
|
||||
particleCount: 200
|
||||
|
@ -458,12 +435,6 @@ particleKinds['level-glacier-game-dev'] = particleKinds['level-glacier-game-dev-
|
|||
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||
colorEnd: hsl 0.7, 0.75, 0.3
|
||||
|
||||
particleKinds['level-glacier-web-dev'] = particleKinds['level-glacier-web-dev-premium'] = ext particleKinds['level-glacier-hero-ladder'],
|
||||
emitter:
|
||||
colorStart: hsl 0.7, 0.25, 0.7
|
||||
colorMiddle: hsl 0.7, 0.25, 0.5
|
||||
colorEnd: hsl 0.7, 0.25, 0.3
|
||||
|
||||
particleKinds['level-volcano-premium-hero'] = ext particleKinds['level-volcano-premium'],
|
||||
emitter:
|
||||
particleCount: 200
|
||||
|
@ -503,9 +474,3 @@ particleKinds['level-volcano-game-dev'] = particleKinds['level-volcano-game-dev-
|
|||
colorStart: hsl 0.7, 0.75, 0.7
|
||||
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||
colorEnd: hsl 0.7, 0.75, 0.3
|
||||
|
||||
particleKinds['level-volcano-web-dev'] = particleKinds['level-volcano-web-dev-premium'] = ext particleKinds['level-volcano-hero-ladder'],
|
||||
emitter:
|
||||
colorStart: hsl 0.7, 0.25, 0.7
|
||||
colorMiddle: hsl 0.7, 0.25, 0.5
|
||||
colorEnd: hsl 0.7, 0.25, 0.3
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
go = (path, options) -> -> @routeDirectly path, arguments, options
|
||||
redirect = (path) -> -> @navigate(path + document.location.search, { trigger: true, replace: true })
|
||||
redirect = (path) -> -> @navigate(path, { trigger: true, replace: true })
|
||||
utils = require './utils'
|
||||
|
||||
module.exports = class CocoRouter extends Backbone.Router
|
||||
|
@ -16,7 +16,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
return @routeDirectly 'play/CampaignView', ['picoctf'], {}
|
||||
if utils.getQueryVariable 'hour_of_code'
|
||||
return @navigate "/play", {trigger: true, replace: true}
|
||||
return @routeDirectly('HomeView', [])
|
||||
return @routeDirectly('NewHomeView', [])
|
||||
|
||||
'about': go('AboutView')
|
||||
|
||||
|
@ -34,10 +34,9 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'admin/design-elements': go('admin/DesignElementsView')
|
||||
'admin/files': go('admin/FilesView')
|
||||
'admin/analytics': go('admin/AnalyticsView')
|
||||
'admin/school-counts': go('admin/SchoolCountsView')
|
||||
'admin/analytics/subscriptions': go('admin/AnalyticsSubscriptionsView')
|
||||
'admin/level-sessions': go('admin/LevelSessionsView')
|
||||
'admin/school-counts': go('admin/SchoolCountsView')
|
||||
'admin/school-licenses': go('admin/SchoolLicensesView')
|
||||
'admin/users': go('admin/UsersView')
|
||||
'admin/base': go('admin/BaseView')
|
||||
'admin/demo-requests': go('admin/DemoRequestsView')
|
||||
|
@ -51,8 +50,8 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'artisans/level-tasks': go('artisans/LevelTasksView')
|
||||
'artisans/solution-problems': go('artisans/SolutionProblemsView')
|
||||
'artisans/thang-tasks': go('artisans/ThangTasksView')
|
||||
'artisans/level-concepts': go('artisans/LevelConceptMap')
|
||||
'artisans/level-guides': go('artisans/LevelGuidesView')
|
||||
|
||||
'beta': go('HomeView')
|
||||
|
||||
'careers': => window.location.href = 'https://jobs.lever.co/codecombat'
|
||||
'Careers': => window.location.href = 'https://jobs.lever.co/codecombat'
|
||||
|
@ -72,15 +71,15 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'contribute/diplomat': go('contribute/DiplomatView')
|
||||
'contribute/scribe': go('contribute/ScribeView')
|
||||
|
||||
'courses': redirect('/students') # Redirected 9/3/16
|
||||
'Courses': redirect('/students') # Redirected 9/3/16
|
||||
'courses/students': redirect('/students') # Redirected 9/3/16
|
||||
'courses': go('courses/CoursesView')
|
||||
'Courses': go('courses/CoursesView')
|
||||
'courses/students': redirect('/courses')
|
||||
'courses/teachers': redirect('/teachers/classes')
|
||||
'courses/purchase': redirect('/teachers/licenses')
|
||||
'courses/enroll(/:courseID)': redirect('/teachers/licenses')
|
||||
'courses/update-account': redirect('students/update-account') # Redirected 9/3/16
|
||||
'courses/:classroomID': -> @navigate("/students/#{arguments[0]}", {trigger: true, replace: true}) # Redirected 9/3/16
|
||||
'courses/:courseID/:courseInstanceID': -> @navigate("/students/#{arguments[0]}/#{arguments[1]}", {trigger: true, replace: true}) # Redirected 9/3/16
|
||||
'courses/update-account': go('courses/CoursesUpdateAccountView')
|
||||
'courses/:classroomID': go('courses/ClassroomView', { studentsOnly: true })
|
||||
'courses/:courseID/:courseInstanceID': go('courses/CourseDetailsView', { studentsOnly: true })
|
||||
|
||||
'db/*path': 'routeToServer'
|
||||
'demo(/*subpath)': go('DemoView')
|
||||
|
@ -104,8 +103,6 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'editor/thang-tasks': go('editor/ThangTasksView')
|
||||
'editor/verifier': go('editor/verifier/VerifierView')
|
||||
'editor/verifier/:levelID': go('editor/verifier/VerifierView')
|
||||
'editor/course': go('editor/course/CourseSearchView')
|
||||
'editor/course/:courseID': go('editor/course/CourseEditView')
|
||||
|
||||
'file/*path': 'routeToServer'
|
||||
|
||||
|
@ -114,7 +111,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'hoc': ->
|
||||
# Matching /?hour_of_code=true behavior
|
||||
@navigate "/play", {trigger: true, replace: true}
|
||||
'home': go('HomeView')
|
||||
'home': go('NewHomeView')
|
||||
|
||||
'i18n': go('i18n/I18NHomeView')
|
||||
'i18n/thang/:handle': go('i18n/I18NEditThangTypeView')
|
||||
|
@ -123,19 +120,18 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'i18n/achievement/:handle': go('i18n/I18NEditAchievementView')
|
||||
'i18n/campaign/:handle': go('i18n/I18NEditCampaignView')
|
||||
'i18n/poll/:handle': go('i18n/I18NEditPollView')
|
||||
'i18n/course/:handle': go('i18n/I18NEditCourseView')
|
||||
|
||||
'identify': go('user/IdentifyView')
|
||||
|
||||
'legal': go('LegalView')
|
||||
|
||||
'multiplayer': go('MultiplayerView')
|
||||
|
||||
'play(/)': go('play/CampaignView') # extra slash is to get Facebook app to work
|
||||
'play/ladder/:levelID/:leagueType/:leagueID': go('ladder/LadderView')
|
||||
'play/ladder/:levelID': go('ladder/LadderView')
|
||||
'play/ladder': go('ladder/MainLadderView')
|
||||
'play/level/:levelID': go('play/level/PlayLevelView')
|
||||
'play/game-dev-level/:levelID/:sessionID': go('play/level/PlayGameDevLevelView')
|
||||
'play/web-dev-level/:levelID/:sessionID': go('play/level/PlayWebDevLevelView')
|
||||
'play/spectate/:levelID': go('play/SpectateView')
|
||||
'play/:map': go('play/CampaignView')
|
||||
|
||||
|
@ -143,34 +139,24 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
|
||||
'privacy': go('PrivacyView')
|
||||
|
||||
'schools': go('HomeView')
|
||||
'seen': go('HomeView')
|
||||
'SEEN': go('HomeView')
|
||||
|
||||
'students': go('courses/CoursesView', { redirectTeachers: true })
|
||||
'students/update-account': go('courses/CoursesUpdateAccountView', { redirectTeachers: true })
|
||||
'students/:classroomID': go('courses/ClassroomView', { redirectTeachers: true, studentsOnly: true })
|
||||
'students/:courseID/:courseInstanceID': go('courses/CourseDetailsView', { redirectTeachers: true, studentsOnly: true })
|
||||
'schools': go('NewHomeView')
|
||||
'seen': go('NewHomeView')
|
||||
'SEEN': go('NewHomeView')
|
||||
|
||||
'teachers': redirect('/teachers/classes')
|
||||
'teachers/classes': go('courses/TeacherClassesView', { redirectStudents: true, teachersOnly: true })
|
||||
'teachers/classes/:classroomID': go('courses/TeacherClassView', { redirectStudents: true, teachersOnly: true })
|
||||
'teachers/courses': go('courses/TeacherCoursesView', { redirectStudents: true })
|
||||
'teachers/course-solution/:courseID/:language': go('teachers/TeacherCourseSolutionView', { redirectStudents: true })
|
||||
'teachers/demo': go('teachers/RequestQuoteView', { redirectStudents: true })
|
||||
'teachers/classes': go('courses/TeacherClassesView', { teachersOnly: true })
|
||||
'teachers/classes/:classroomID': go('courses/TeacherClassView', { teachersOnly: true })
|
||||
'teachers/courses': go('courses/TeacherCoursesView')
|
||||
'teachers/demo': go('teachers/RequestQuoteView')
|
||||
'teachers/enrollments': redirect('/teachers/licenses')
|
||||
'teachers/licenses': go('courses/EnrollmentsView', { redirectStudents: true, teachersOnly: true })
|
||||
'teachers/freetrial': go('teachers/RequestQuoteView', { redirectStudents: true })
|
||||
'teachers/licenses': go('courses/EnrollmentsView', { teachersOnly: true })
|
||||
'teachers/freetrial': go('teachers/RequestQuoteView')
|
||||
'teachers/quote': redirect('/teachers/demo')
|
||||
'teachers/resources': go('teachers/ResourceHubView', { redirectStudents: true })
|
||||
'teachers/resources/:name': go('teachers/MarkdownResourceView', { redirectStudents: true })
|
||||
'teachers/signup': ->
|
||||
return @routeDirectly('teachers/CreateTeacherAccountView', []) if me.isAnonymous()
|
||||
return @navigate('/students', {trigger: true, replace: true}) if me.isStudent() and not me.isAdmin()
|
||||
@navigate('/teachers/update-account', {trigger: true, replace: true})
|
||||
'teachers/update-account': ->
|
||||
return @navigate('/teachers/signup', {trigger: true, replace: true}) if me.isAnonymous()
|
||||
return @navigate('/students', {trigger: true, replace: true}) if me.isStudent() and not me.isAdmin()
|
||||
@routeDirectly('teachers/ConvertToTeacherAccountView', [])
|
||||
|
||||
'test(/*subpath)': go('TestView')
|
||||
|
@ -188,10 +174,6 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
@navigate e, {trigger: true}
|
||||
|
||||
routeDirectly: (path, args=[], options={}) ->
|
||||
if options.redirectStudents and me.isStudent() and not me.isAdmin()
|
||||
return @navigate('/students', {trigger: true, replace: true})
|
||||
if options.redirectTeachers and me.isTeacher() and not me.isAdmin()
|
||||
return @navigate('/teachers', {trigger: true, replace: true})
|
||||
if options.teachersOnly and not (me.isTeacher() or me.isAdmin())
|
||||
return @routeDirectly('teachers/RestrictedToTeachersView')
|
||||
if options.studentsOnly and not (me.isStudent() or me.isAdmin())
|
||||
|
@ -210,7 +192,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
@listenToOnce application.moduleLoader, 'load-complete', ->
|
||||
@routeDirectly(path, args, options)
|
||||
return
|
||||
return go('NotFoundView') if not ViewClass
|
||||
return @openView @notFoundView() if not ViewClass
|
||||
view = new ViewClass(options, args...) # options, then any path fragment args
|
||||
view.render()
|
||||
@openView(view)
|
||||
|
|
|
@ -96,7 +96,7 @@ module.exports = class Tracker extends CocoClass
|
|||
|
||||
trackPageView: (includeIntegrations=[]) ->
|
||||
includeMixpanel = (name) ->
|
||||
mixpanelIncludes = []
|
||||
mixpanelIncludes = ['', 'schools', 'play', 'play/level/dungeons-of-kithgard']
|
||||
name in mixpanelIncludes or /courses|students|teachers/ig.test(name)
|
||||
|
||||
name = Backbone.history.getFragment()
|
||||
|
@ -181,7 +181,6 @@ module.exports = class Tracker extends CocoClass
|
|||
ga? 'send', 'timing', category, variable, duration, label
|
||||
|
||||
updateRole: ->
|
||||
return if me.isAdmin()
|
||||
return unless me.isTeacher()
|
||||
return require('core/services/segment')() unless @segmentLoaded
|
||||
@identify()
|
||||
|
|
|
@ -44,73 +44,42 @@ window.console ?=
|
|||
debug: ->
|
||||
console.debug ?= console.log # Needed for IE10 and earlier
|
||||
|
||||
Application = {
|
||||
initialize: ->
|
||||
Router = require('core/Router')
|
||||
@isProduction = -> document.location.href.search('https?://localhost') is -1
|
||||
@isIPadApp = webkit?.messageHandlers? and navigator.userAgent?.indexOf('CodeCombat-iPad') isnt -1
|
||||
$('body').addClass 'ipad' if @isIPadApp
|
||||
$('body').addClass 'picoctf' if window.serverConfig.picoCTF
|
||||
if $.browser.msie and parseInt($.browser.version) is 10
|
||||
$("html").addClass("ie10")
|
||||
@tracker = new Tracker()
|
||||
@facebookHandler = new FacebookHandler()
|
||||
@gplusHandler = new GPlusHandler()
|
||||
@githubHandler = new GitHubHandler()
|
||||
@moduleLoader = new ModuleLoader()
|
||||
@moduleLoader.loadLanguage(me.get('preferredLanguage', true))
|
||||
$(document).bind 'keydown', preventBackspace
|
||||
preload(COMMON_FILES)
|
||||
CocoModel.pollAchievements()
|
||||
unless me.get('anonymous')
|
||||
# TODO: Remove logging later, once this system has proved stable
|
||||
me.on 'change:earned', (user, newEarned) ->
|
||||
newEarned ?= {}
|
||||
oldEarned = user.previous('earned') ? {}
|
||||
if oldEarned.gems isnt newEarned.gems
|
||||
console.log 'Gems changed', oldEarned.gems, '->', newEarned.gems
|
||||
newLevels = _.difference(newEarned.levels, oldEarned.levels)
|
||||
if newLevels.length
|
||||
console.log 'Levels added', newLevels
|
||||
newItems = _.difference(newEarned.items, oldEarned.items)
|
||||
if newItems.length
|
||||
console.log 'Items added', newItems
|
||||
newHeroes = _.difference(newEarned.heroes, oldEarned.heroes)
|
||||
if newHeroes.length
|
||||
console.log 'Heroes added', newHeroes
|
||||
me.on 'change:points', (user, newPoints) ->
|
||||
console.log 'Points changed', user.previous('points'), '->', newPoints
|
||||
@checkForNewAchievement()
|
||||
$.i18n.init {
|
||||
lng: me.get('preferredLanguage', true)
|
||||
fallbackLng: 'en'
|
||||
resStore: locale
|
||||
useDataAttrOptions: true
|
||||
#debug: true
|
||||
#sendMissing: true
|
||||
#sendMissingTo: 'current'
|
||||
#resPostPath: '/languages/add/__lng__/__ns__'
|
||||
}, (t) =>
|
||||
@router = new Router()
|
||||
onIdleChanged = (to) => => Backbone.Mediator.publish 'application:idle-changed', idle: @userIsIdle = to
|
||||
@idleTracker = new Idle
|
||||
onAway: onIdleChanged true
|
||||
onAwayBack: onIdleChanged false
|
||||
onHidden: onIdleChanged true
|
||||
onVisible: onIdleChanged false
|
||||
awayTimeout: 5 * 60 * 1000
|
||||
@idleTracker.start()
|
||||
|
||||
checkForNewAchievement: ->
|
||||
if me.get('lastAchievementChecked')
|
||||
startFrom = new Date(me.get('lastAchievementChecked'))
|
||||
else
|
||||
startFrom = me.created()
|
||||
|
||||
daysSince = moment.duration(new Date() - startFrom).asDays()
|
||||
if daysSince > 1
|
||||
me.checkForNewAchievement().then => @checkForNewAchievement()
|
||||
}
|
||||
Application = initialize: ->
|
||||
Router = require('core/Router')
|
||||
@isProduction = -> document.location.href.search('https?://localhost') is -1
|
||||
@isIPadApp = webkit?.messageHandlers? and navigator.userAgent?.indexOf('CodeCombat-iPad') isnt -1
|
||||
$('body').addClass 'ipad' if @isIPadApp
|
||||
$('body').addClass 'picoctf' if window.serverConfig.picoCTF
|
||||
if $.browser.msie and parseInt($.browser.version) is 10
|
||||
$("html").addClass("ie10")
|
||||
@tracker = new Tracker()
|
||||
@facebookHandler = new FacebookHandler()
|
||||
@gplusHandler = new GPlusHandler()
|
||||
@githubHandler = new GitHubHandler()
|
||||
@moduleLoader = new ModuleLoader()
|
||||
@moduleLoader.loadLanguage(me.get('preferredLanguage', true))
|
||||
$(document).bind 'keydown', preventBackspace
|
||||
preload(COMMON_FILES)
|
||||
CocoModel.pollAchievements()
|
||||
$.i18n.init {
|
||||
lng: me.get('preferredLanguage', true)
|
||||
fallbackLng: 'en'
|
||||
resStore: locale
|
||||
useDataAttrOptions: true
|
||||
#debug: true
|
||||
#sendMissing: true
|
||||
#sendMissingTo: 'current'
|
||||
#resPostPath: '/languages/add/__lng__/__ns__'
|
||||
}, (t) =>
|
||||
@router = new Router()
|
||||
onIdleChanged = (to) => => Backbone.Mediator.publish 'application:idle-changed', idle: @userIsIdle = to
|
||||
@idleTracker = new Idle
|
||||
onAway: onIdleChanged true
|
||||
onAwayBack: onIdleChanged false
|
||||
onHidden: onIdleChanged true
|
||||
onVisible: onIdleChanged false
|
||||
awayTimeout: 5 * 60 * 1000
|
||||
@idleTracker.start()
|
||||
|
||||
module.exports = Application
|
||||
window.application = Application
|
||||
|
|
|
@ -89,8 +89,8 @@ expandFlattenedDelta = (delta, left, schema) ->
|
|||
delta
|
||||
|
||||
module.exports.makeJSONDiffer = ->
|
||||
objectHash = (obj) -> if obj? then (obj.name or obj.id or obj._id or JSON.stringify(_.keys(obj))) else 'null'
|
||||
jsondiffpatch.create({objectHash})
|
||||
hasher = (obj) -> if obj? then obj.name or obj.id or obj._id or JSON.stringify(_.keys(obj)) else 'null'
|
||||
jsondiffpatch.create({objectHash: hasher})
|
||||
|
||||
module.exports.getConflicts = (headDeltas, pendingDeltas) ->
|
||||
# headDeltas and pendingDeltas should be lists of deltas returned by expandDelta
|
||||
|
@ -179,4 +179,4 @@ prunePath = (delta, path) ->
|
|||
module.exports.DOC_SKIP_PATHS = [
|
||||
'_id','version', 'commitMessage', 'parent', 'created',
|
||||
'slug', 'index', '__v', 'patches', 'creator', 'js', 'watchers', 'levelsUpdated'
|
||||
]
|
||||
]
|
|
@ -45,7 +45,7 @@ module.exports.applyErrorsToForm = (el, errors, warning=false) ->
|
|||
for error in errors
|
||||
if error.code is tv4.errorCodes.OBJECT_REQUIRED
|
||||
prop = _.last(_.string.words(error.message)) # hack
|
||||
message = $.i18n.t('common.required_field')
|
||||
message = 'Required field'
|
||||
|
||||
else if error.dataPath
|
||||
prop = error.dataPath[1..]
|
||||
|
|
|
@ -8,12 +8,12 @@ channelSchemas =
|
|||
'errors': require 'schemas/subscriptions/errors'
|
||||
'ipad': require 'schemas/subscriptions/ipad'
|
||||
'misc': require 'schemas/subscriptions/misc'
|
||||
'multiplayer': require 'schemas/subscriptions/multiplayer'
|
||||
'play': require 'schemas/subscriptions/play'
|
||||
'surface': require 'schemas/subscriptions/surface'
|
||||
'tome': require 'schemas/subscriptions/tome'
|
||||
'god': require 'schemas/subscriptions/god'
|
||||
'scripts': require 'schemas/subscriptions/scripts'
|
||||
'web-dev': require 'schemas/subscriptions/web-dev'
|
||||
'world': require 'schemas/subscriptions/world'
|
||||
|
||||
definitionSchemas =
|
||||
|
@ -165,5 +165,5 @@ window.onbeforeunload = (e) ->
|
|||
return leavingMessage
|
||||
else
|
||||
return
|
||||
|
||||
|
||||
$ -> init()
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
publishableKey = if application.isProduction() then 'pk_live_27jQZozjDGN1HSUTnSuM578g' else 'pk_test_zG5UwVu6Ww8YhtE9ZYh0JO6a'
|
||||
|
||||
if me.isAnonymous()
|
||||
module.exports = {}
|
||||
else if not StripeCheckout?
|
||||
module.exports = {}
|
||||
console.error "Failure loading StripeCheckout API, returning empty object."
|
||||
else
|
||||
if StripeCheckout?
|
||||
module.exports = handler = StripeCheckout.configure({
|
||||
key: publishableKey
|
||||
name: 'CodeCombat'
|
||||
|
@ -16,4 +11,7 @@ else
|
|||
Backbone.Mediator.publish 'stripe:received-token', { token: token }
|
||||
locale: 'auto'
|
||||
})
|
||||
_.extend(handler, Backbone.Events)
|
||||
else
|
||||
module.exports = {}
|
||||
console.error "Failure loading StripeCheckout API, returning empty object."
|
||||
_.extend(handler, Backbone.Events)
|
||||
|
|
|
@ -55,7 +55,7 @@ module.exports = FacebookHandler = class FacebookHandler extends CocoClass
|
|||
js = d.createElement('script')
|
||||
js.id = id
|
||||
js.async = true
|
||||
js.src = '//connect.facebook.net/en_US/sdk.js'
|
||||
js.src = '//connect.facebook.net/en_US/all.js'
|
||||
|
||||
#js.src = '//connect.facebook.net/en_US/all/debug.js'
|
||||
ref.parentNode.insertBefore js, ref
|
||||
|
@ -63,13 +63,12 @@ module.exports = FacebookHandler = class FacebookHandler extends CocoClass
|
|||
)(document)
|
||||
|
||||
window.fbAsyncInit = =>
|
||||
FB.init({
|
||||
FB.init
|
||||
appId: (if document.location.origin is 'http://localhost:3000' then '607435142676437' else '148832601965463') # App ID
|
||||
channelUrl: document.location.origin + '/channel.html' # Channel File
|
||||
cookie: true # enable cookies to allow the server to access the session
|
||||
xfbml: true # parse XFBML
|
||||
version: 'v2.7'
|
||||
})
|
||||
|
||||
FB.getLoginStatus (response) =>
|
||||
if response.status is 'connected'
|
||||
@connected = true
|
||||
|
|
|
@ -2,7 +2,6 @@ CocoModel = require 'models/CocoModel'
|
|||
CocoCollection = require 'collections/CocoCollection'
|
||||
{me} = require('core/auth')
|
||||
locale = require 'locale/locale'
|
||||
utils = require 'core/utils'
|
||||
|
||||
initializeFilePicker = ->
|
||||
require('core/services/filepicker')() unless window.application.isIPadApp
|
||||
|
@ -235,14 +234,21 @@ class ImageFileTreema extends TreemaNode.nodeMap.string
|
|||
@refreshDisplay()
|
||||
|
||||
|
||||
codeLanguages =
|
||||
javascript: 'ace/mode/javascript'
|
||||
coffeescript: 'ace/mode/coffee'
|
||||
python: 'ace/mode/python'
|
||||
lua: 'ace/mode/lua'
|
||||
java: 'ace/mode/java'
|
||||
|
||||
class CodeLanguagesObjectTreema extends TreemaNode.nodeMap.object
|
||||
childPropertiesAvailable: ->
|
||||
(key for key in _.keys(utils.aceEditModes) when not @data[key]? and not (key is 'javascript' and @workingSchema.skipJavaScript))
|
||||
(key for key in _.keys(codeLanguages) when not @data[key]? and not (key is 'javascript' and @workingSchema.skipJavaScript))
|
||||
|
||||
class CodeLanguageTreema extends TreemaNode.nodeMap.string
|
||||
buildValueForEditing: (valEl, data) ->
|
||||
super(valEl, data)
|
||||
valEl.find('input').autocomplete(source: _.keys(utils.aceEditModes), minLength: 0, delay: 0, autoFocus: true)
|
||||
valEl.find('input').autocomplete(source: _.keys(codeLanguages), minLength: 0, delay: 0, autoFocus: true)
|
||||
valEl
|
||||
|
||||
class CodeTreema extends TreemaNode.nodeMap.ace
|
||||
|
@ -250,8 +256,8 @@ class CodeTreema extends TreemaNode.nodeMap.ace
|
|||
super(arguments...)
|
||||
@workingSchema.aceTabSize = 4
|
||||
# TODO: Find a less hacky solution for this
|
||||
@workingSchema.aceMode = mode if mode = utils.aceEditModes[@keyForParent]
|
||||
@workingSchema.aceMode = mode if mode = utils.aceEditModes[@parent?.data?.language]
|
||||
@workingSchema.aceMode = mode if mode = codeLanguages[@keyForParent]
|
||||
@workingSchema.aceMode = mode if mode = codeLanguages[@parent?.data?.language]
|
||||
|
||||
class CoffeeTreema extends CodeTreema
|
||||
constructor: ->
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
module.exports =
|
||||
playDevLevel: ({level, session, course}) ->
|
||||
shareURL = "#{window.location.origin}/play/#{level.get('type')}-level/#{level.get('slug')}/#{session.id}"
|
||||
shareURL += "?course=#{course.id}" if course
|
||||
return shareURL
|
||||
|
||||
courseArenaLadder: ({level, courseInstance}) ->
|
||||
"/play/ladder/#{level.get('slug')}/course/#{courseInstance.id}"
|
||||
|
||||
courseLevel: ({level, courseInstance}) ->
|
||||
url = "/play/level/#{level.get('slug')}?course=#{courseInstance.get('courseID')}&course-instance=#{courseInstance.id}"
|
||||
url += "&codeLanguage=#{level.get('primerLanguage')}" if level.get('primerLanguage')
|
||||
url
|
|
@ -18,17 +18,6 @@ module.exports.combineAncestralObject = (obj, propertyName) ->
|
|||
obj = Object.getPrototypeOf(obj)
|
||||
combined
|
||||
|
||||
module.exports.courseIDs = courseIDs =
|
||||
INTRODUCTION_TO_COMPUTER_SCIENCE: '560f1a9f22961295f9427742'
|
||||
COMPUTER_SCIENCE_2: '5632661322961295f9428638'
|
||||
GAME_DEVELOPMENT_1: '5789587aad86a6efb573701e'
|
||||
WEB_DEVELOPMENT_1: '5789587aad86a6efb573701f'
|
||||
COMPUTER_SCIENCE_3: '56462f935afde0c6fd30fc8c'
|
||||
GAME_DEVELOPMENT_2: '57b621e7ad86a6efb5737e64'
|
||||
WEB_DEVELOPMENT_2: '5789587aad86a6efb5737020'
|
||||
COMPUTER_SCIENCE_4: '56462f935afde0c6fd30fc8d'
|
||||
COMPUTER_SCIENCE_5: '569ed916efa72b0ced971447'
|
||||
|
||||
module.exports.normalizeFunc = (func_thing, object) ->
|
||||
# func could be a string to a function in this class
|
||||
# or a function in its own right
|
||||
|
@ -60,10 +49,6 @@ toHex = (n) ->
|
|||
h = '0'+h if h.length is 1
|
||||
h
|
||||
|
||||
module.exports.pathToUrl = (path) ->
|
||||
base = location.protocol + '//' + location.hostname + (location.port && ":" + location.port)
|
||||
base + path
|
||||
|
||||
module.exports.i18n = (say, target, language=me.get('preferredLanguage', true), fallback='en') ->
|
||||
generalResult = null
|
||||
fallBackResult = null
|
||||
|
@ -199,10 +184,6 @@ if document?.createElement
|
|||
return
|
||||
)(document)
|
||||
|
||||
# So that we can stub out userAgent in tests
|
||||
module.exports.userAgent = ->
|
||||
window.navigator.userAgent
|
||||
|
||||
module.exports.getQueryVariable = getQueryVariable = (param, defaultValue) ->
|
||||
query = document.location.search.substring 1
|
||||
pairs = (pair.split('=') for pair in query.split '&')
|
||||
|
@ -278,7 +259,7 @@ startsWithVowel = (s) -> s[0] in 'aeiouAEIOU'
|
|||
module.exports.filterMarkdownCodeLanguages = (text, language) ->
|
||||
return '' unless text
|
||||
currentLanguage = language or me.get('aceConfig')?.language or 'python'
|
||||
excludedLanguages = _.without ['javascript', 'python', 'coffeescript', 'clojure', 'lua', 'java', 'io', 'html'], currentLanguage
|
||||
excludedLanguages = _.without ['javascript', 'python', 'coffeescript', 'clojure', 'lua', 'java', 'io'], currentLanguage
|
||||
# Exclude language-specific code blocks like ```python (... code ...)``` for each non-target language.
|
||||
codeBlockExclusionRegex = new RegExp "```(#{excludedLanguages.join('|')})\n[^`]+```\n?", 'gm'
|
||||
# Exclude language-specific images like  for each non-target language.
|
||||
|
@ -309,15 +290,13 @@ module.exports.filterMarkdownCodeLanguages = (text, language) ->
|
|||
return text
|
||||
|
||||
module.exports.aceEditModes = aceEditModes =
|
||||
javascript: 'ace/mode/javascript'
|
||||
coffeescript: 'ace/mode/coffee'
|
||||
python: 'ace/mode/python'
|
||||
lua: 'ace/mode/lua'
|
||||
java: 'ace/mode/java'
|
||||
html: 'ace/mode/html'
|
||||
'javascript': 'ace/mode/javascript'
|
||||
'coffeescript': 'ace/mode/coffee'
|
||||
'python': 'ace/mode/python'
|
||||
'java': 'ace/mode/java'
|
||||
'lua': 'ace/mode/lua'
|
||||
'java': 'ace/mode/java'
|
||||
|
||||
# These ACEs are used for displaying code snippets statically, like in SpellPaletteEntryView popovers
|
||||
# and have short lifespans
|
||||
module.exports.initializeACE = (el, codeLanguage) ->
|
||||
contents = $(el).text().trim()
|
||||
editor = ace.edit el
|
||||
|
@ -346,7 +325,6 @@ module.exports.capitalLanguages = capitalLanguages =
|
|||
'python': 'Python'
|
||||
'java': 'Java'
|
||||
'lua': 'Lua'
|
||||
'html': 'HTML'
|
||||
|
||||
module.exports.createLevelNumberMap = (levels) ->
|
||||
levelNumberMap = {}
|
||||
|
@ -393,24 +371,6 @@ module.exports.findNextLevel = (levels, currentIndex, needsPractice) ->
|
|||
module.exports.needsPractice = (playtime=0, threshold=2) ->
|
||||
playtime / 60 > threshold
|
||||
|
||||
module.exports.sortCourses = (courses) ->
|
||||
orderedIDs = [
|
||||
courseIDs.INTRODUCTION_TO_COMPUTER_SCIENCE
|
||||
courseIDs.COMPUTER_SCIENCE_2
|
||||
courseIDs.GAME_DEVELOPMENT_1
|
||||
courseIDs.WEB_DEVELOPMENT_1
|
||||
courseIDs.COMPUTER_SCIENCE_3
|
||||
courseIDs.GAME_DEVELOPMENT_2
|
||||
courseIDs.WEB_DEVELOPMENT_2
|
||||
courseIDs.COMPUTER_SCIENCE_4
|
||||
courseIDs.COMPUTER_SCIENCE_5
|
||||
]
|
||||
_.sortBy courses, (course) ->
|
||||
# ._id can be from classroom.courses, otherwise it's probably .id
|
||||
index = orderedIDs.indexOf(course.id ? course._id)
|
||||
index = 9001 if index is -1
|
||||
index
|
||||
|
||||
module.exports.usStateCodes =
|
||||
# https://github.com/mdzhang/us-state-codes
|
||||
# generated by js2coffee 2.2.0
|
||||
|
@ -510,4 +470,3 @@ module.exports.usStateCodes =
|
|||
getStateCodeByStateName: getStateCodeByStateName
|
||||
}
|
||||
)()
|
||||
|
||||
|
|
|
@ -111,9 +111,7 @@ module.exports = class Angel extends CocoClass
|
|||
when 'user-code-problem'
|
||||
@publishGodEvent 'user-code-problem', problem: event.data.problem
|
||||
when 'world-load-progress-changed'
|
||||
progress = event.data.progress
|
||||
progress = Math.min(progress, 0.9) if @work.indefiniteLength
|
||||
@publishGodEvent 'world-load-progress-changed', { progress }
|
||||
@publishGodEvent 'world-load-progress-changed', progress: event.data.progress
|
||||
unless event.data.progress is 1 or @work.preload or @work.headless or @work.synchronous or @deserializationQueue.length or (@shared.firstWorld and not @shared.spectate)
|
||||
@worker.postMessage func: 'serializeFramesSoFar' # Stream it!
|
||||
|
||||
|
@ -140,7 +138,6 @@ module.exports = class Angel extends CocoClass
|
|||
return if @aborting
|
||||
# Toggle BOX2D_ENABLED during deserialization so that if we have box2d in the namespace, the Collides Components still don't try to create bodies for deserialized Thangs upon attachment.
|
||||
window.BOX2D_ENABLED = false
|
||||
streamingWorld?.indefiniteLength = @work.indefiniteLength
|
||||
@streamingWorld = World.deserialize serialized, @shared.worldClassMap, @shared.lastSerializedWorldFrames, @finishBeholdingWorld(goalStates), startFrame, endFrame, @work.level, streamingWorld
|
||||
window.BOX2D_ENABLED = true
|
||||
@shared.lastSerializedWorldFrames = serialized.frames
|
||||
|
@ -148,10 +145,7 @@ module.exports = class Angel extends CocoClass
|
|||
finishBeholdingWorld: (goalStates) -> (world) =>
|
||||
return if @aborting or @destroyed
|
||||
finished = world.frames.length is world.totalFrames
|
||||
if @work?.indefiniteLength and world.victory?
|
||||
finished = true
|
||||
world.totalFrames = world.frames.length
|
||||
firstChangedFrame = if @work?.indefiniteLength then 0 else world.findFirstChangedFrame @shared.world
|
||||
firstChangedFrame = world.findFirstChangedFrame @shared.world
|
||||
eventType = if finished then 'new-world-created' else 'streaming-world-updated'
|
||||
if finished
|
||||
@shared.world = world
|
||||
|
@ -234,7 +228,7 @@ module.exports = class Angel extends CocoClass
|
|||
@running = false
|
||||
@work = null
|
||||
@streamingWorld = null
|
||||
@deserializationQueue = []
|
||||
@deserializationQueue = null
|
||||
_.remove @shared.busyAngels, @
|
||||
@abortTimeout = _.delay @fireWorker, @abortTimeoutDuration
|
||||
@aborting = true
|
||||
|
@ -254,7 +248,7 @@ module.exports = class Angel extends CocoClass
|
|||
@initialized = false
|
||||
@work = null
|
||||
@streamingWorld = null
|
||||
@deserializationQueue = []
|
||||
@deserializationQueue = null
|
||||
@hireWorker() if rehire
|
||||
|
||||
hireWorker: ->
|
||||
|
|
|
@ -22,7 +22,6 @@ module.exports = Bus = class Bus extends CocoClass
|
|||
'auth:me-synced': 'onMeSynced'
|
||||
|
||||
connect: ->
|
||||
# Put Firebase back in bower if you want to use this
|
||||
Backbone.Mediator.publish 'bus:connecting', {bus: @}
|
||||
Firebase.goOnline()
|
||||
@fireRef = new Firebase(Bus.fireHost + '/' + @docName)
|
||||
|
|
|
@ -20,7 +20,6 @@ module.exports = class God extends CocoClass
|
|||
options ?= {}
|
||||
@retrieveValueFromFrame = _.throttle @retrieveValueFromFrame, 1000
|
||||
@gameUIState ?= options.gameUIState or new GameUIState()
|
||||
@indefiniteLength = options.indefiniteLength or false
|
||||
super()
|
||||
|
||||
# Angels are all given access to this.
|
||||
|
@ -72,9 +71,9 @@ module.exports = class God extends CocoClass
|
|||
@lastFixedSeed = e.fixedSeed
|
||||
@lastFlagHistory = (flag for flag in e.flagHistory when flag.source isnt 'code')
|
||||
@lastDifficulty = e.difficulty
|
||||
@createWorld e.spells, e.preload, e.realTime, e.justBegin
|
||||
@createWorld e.spells, e.preload, e.realTime
|
||||
|
||||
createWorld: (spells, preload, realTime, justBegin) ->
|
||||
createWorld: (spells, preload, realTime) ->
|
||||
console.log "#{@nick}: Let there be light upon #{@level.name}! (preload: #{preload})"
|
||||
userCodeMap = @getUserCodeMap spells
|
||||
|
||||
|
@ -95,9 +94,9 @@ module.exports = class God extends CocoClass
|
|||
return if hadPreloader
|
||||
|
||||
@angelsShare.workQueue = []
|
||||
work = {
|
||||
work =
|
||||
userCodeMap: userCodeMap
|
||||
@level
|
||||
level: @level
|
||||
levelSessionIDs: @levelSessionIDs
|
||||
submissionCount: @lastSubmissionCount
|
||||
fixedSeed: @lastFixedSeed
|
||||
|
@ -105,12 +104,9 @@ module.exports = class God extends CocoClass
|
|||
difficulty: @lastDifficulty
|
||||
goals: @angelsShare.goalManager?.getGoals()
|
||||
headless: @angelsShare.headless
|
||||
preload
|
||||
preload: preload
|
||||
synchronous: not Worker? # Profiling world simulation is easier on main thread, or we are IE9.
|
||||
realTime
|
||||
justBegin
|
||||
indefiniteLength: @indefiniteLength and realTime
|
||||
}
|
||||
realTime: realTime
|
||||
@angelsShare.workQueue.push work
|
||||
angel.workIfIdle() for angel in @angelsShare.angels
|
||||
work
|
||||
|
@ -118,7 +114,9 @@ module.exports = class God extends CocoClass
|
|||
getUserCodeMap: (spells) ->
|
||||
userCodeMap = {}
|
||||
for spellKey, spell of spells
|
||||
(userCodeMap[spell.thang.thang.id] ?= {})[spell.name] = spell.thang.aether.serialize()
|
||||
for thangID, spellThang of spell.thangs
|
||||
continue if spellThang.thang?.programmableMethods[spell.name].cloneOf
|
||||
(userCodeMap[thangID] ?= {})[spell.name] = spellThang.aether.serialize()
|
||||
userCodeMap
|
||||
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ module.exports = class LevelBus extends Bus
|
|||
@fireScriptsRef = @fireRef?.child('scripts')
|
||||
|
||||
setSession: (@session) ->
|
||||
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
|
||||
@timerIntervalID = setInterval(@incrementSessionPlaytime, 1000)
|
||||
|
||||
onIdleChanged: (e) ->
|
||||
|
@ -52,7 +53,8 @@ module.exports = class LevelBus extends Bus
|
|||
@session.set('playtime', (@session.get('playtime') ? 0) + 1)
|
||||
|
||||
onPoint: ->
|
||||
return true
|
||||
return true unless @session?.get('multiplayer')
|
||||
super()
|
||||
|
||||
onMeSynced: =>
|
||||
super()
|
||||
|
@ -234,11 +236,17 @@ module.exports = class LevelBus extends Bus
|
|||
@changedSessionProperties.chat = true
|
||||
@saveSession()
|
||||
|
||||
onMultiplayerChanged: ->
|
||||
@changedSessionProperties.multiplayer = true
|
||||
@session.updatePermissions()
|
||||
@changedSessionProperties.permissions = true
|
||||
@saveSession()
|
||||
|
||||
# Debounced as saveSession
|
||||
reallySaveSession: ->
|
||||
return if _.isEmpty @changedSessionProperties
|
||||
# don't let peeking admins mess with the session accidentally
|
||||
return unless @session.get('creator') is me.id
|
||||
return unless @session.get('multiplayer') or @session.get('creator') is me.id
|
||||
return if @session.fake
|
||||
Backbone.Mediator.publish 'level:session-will-save', session: @session
|
||||
patch = {}
|
||||
|
|
|
@ -12,7 +12,7 @@ app = require 'core/application'
|
|||
World = require 'lib/world/world'
|
||||
utils = require 'core/utils'
|
||||
|
||||
LOG = me.get('name') is 'Shanakin' # Debugging a hanging load issue in production
|
||||
LOG = false
|
||||
|
||||
# This is an initial stab at unifying loading and setup into a single place which can
|
||||
# monitor everything and keep a LoadingScreen visible overall progress.
|
||||
|
@ -22,11 +22,6 @@ LOG = me.get('name') is 'Shanakin' # Debugging a hanging load issue in producti
|
|||
# * Sprite map generation
|
||||
# * Connecting to Firebase
|
||||
|
||||
# LevelLoader depends on SuperModel retrying timed out requests, as these occasionally happen during play.
|
||||
# If LevelLoader ever moves away from SuperModel, it will have to manage its own retries.
|
||||
|
||||
reportedLoadErrorAlready = false
|
||||
|
||||
module.exports = class LevelLoader extends CocoClass
|
||||
|
||||
constructor: (options) ->
|
||||
|
@ -54,21 +49,10 @@ module.exports = class LevelLoader extends CocoClass
|
|||
if @supermodel.finished()
|
||||
@onSupermodelLoaded()
|
||||
else
|
||||
@loadTimeoutID = setTimeout @reportLoadError.bind(@), 30000
|
||||
@listenToOnce @supermodel, 'loaded-all', @onSupermodelLoaded
|
||||
|
||||
# Supermodel (Level) Loading
|
||||
|
||||
loadWorldNecessities: ->
|
||||
# TODO: Actually trigger loading, instead of in the constructor
|
||||
new Promise((resolve, reject) =>
|
||||
return resolve(@) if @world
|
||||
@once 'world-necessities-loaded', => resolve(@)
|
||||
@once 'world-necessity-load-failed', ({resource}) ->
|
||||
{ jqxhr } = resource
|
||||
reject({message: jqxhr.responseJSON?.message or jqxhr.responseText or 'Unknown Error'})
|
||||
)
|
||||
|
||||
loadLevel: ->
|
||||
@level = @supermodel.getModel(Level, @levelID) or new Level _id: @levelID
|
||||
if @level.loaded
|
||||
|
@ -77,25 +61,10 @@ module.exports = class LevelLoader extends CocoClass
|
|||
@level = @supermodel.loadModel(@level, 'level').model
|
||||
@listenToOnce @level, 'sync', @onLevelLoaded
|
||||
|
||||
reportLoadError: ->
|
||||
window.tracker?.trackEvent 'LevelLoadError',
|
||||
category: 'Error',
|
||||
levelSlug: @work?.level?.slug,
|
||||
unloaded: JSON.stringify(@supermodel.report().map (m) -> _.result(m.model, 'url'))
|
||||
|
||||
onLevelLoaded: ->
|
||||
if not @sessionless and @level.isType('hero', 'hero-ladder', 'hero-coop', 'course')
|
||||
if not @sessionless and @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course']
|
||||
@sessionDependenciesRegistered = {}
|
||||
if @level.isType('web-dev')
|
||||
@headless = true
|
||||
if @sessionless
|
||||
# When loading a web-dev level in the level editor, pretend it's a normal hero level so we can put down our placeholder Thang.
|
||||
# TODO: avoid this whole roundabout Thang-based way of doing web-dev levels
|
||||
originalGet = @level.get
|
||||
@level.get = ->
|
||||
return 'hero' if arguments[0] is 'type'
|
||||
originalGet.apply @, arguments
|
||||
if (@courseID and not @level.isType('course', 'course-ladder', 'game-dev', 'web-dev')) or window.serverConfig.picoCTF
|
||||
if (@courseID and @level.get('type', true) not in ['course', 'course-ladder']) or window.serverConfig.picoCTF
|
||||
# Because we now use original hero levels for both hero and course levels, we fake being a course level in this context.
|
||||
originalGet = @level.get
|
||||
@level.get = ->
|
||||
|
@ -153,8 +122,7 @@ module.exports = class LevelLoader extends CocoClass
|
|||
url += "?course=#{@courseID}" if @courseID
|
||||
|
||||
session = new LevelSession().setURL url
|
||||
if @headless and not @level.isType('web-dev')
|
||||
session.project = ['creator', 'team', 'heroConfig', 'codeLanguage', 'submittedCodeLanguage', 'state', 'submittedCode', 'submitted']
|
||||
session.project = ['creator', 'team', 'heroConfig', 'codeLanguage', 'submittedCodeLanguage', 'state', 'submittedCode'] if @headless
|
||||
@sessionResource = @supermodel.loadModel(session, 'level_session', {cache: false})
|
||||
@session = @sessionResource.model
|
||||
if @opponentSessionID
|
||||
|
@ -201,7 +169,7 @@ module.exports = class LevelLoader extends CocoClass
|
|||
@consolidateFlagHistory() if @opponentSession?.loaded
|
||||
else if session is @opponentSession
|
||||
@consolidateFlagHistory() if @session.loaded
|
||||
if @level.isType('course') # course-ladder is hard to handle because there's 2 sessions
|
||||
if @level.get('type', true) in ['course'] # course-ladder is hard to handle because there's 2 sessions
|
||||
heroThangType = me.get('heroConfig')?.thangType or ThangType.heroes.captain
|
||||
console.log "Course mode, loading custom hero: ", heroThangType if LOG
|
||||
url = "/db/thang.type/#{heroThangType}/version"
|
||||
|
@ -209,12 +177,8 @@ module.exports = class LevelLoader extends CocoClass
|
|||
console.log "Pushing resource: ", heroResource if LOG
|
||||
@worldNecessities.push heroResource
|
||||
@sessionDependenciesRegistered[session.id] = true
|
||||
unless @level.isType('hero', 'hero-ladder', 'hero-coop')
|
||||
# Return before loading heroConfig ThangTypes. Finish if all world necessities were completed by the time the session loaded.
|
||||
if @checkAllWorldNecessitiesRegisteredAndLoaded()
|
||||
@onWorldNecessitiesLoaded()
|
||||
return
|
||||
# Load the ThangTypes needed for the session's heroConfig for these types of levels
|
||||
return unless @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop']
|
||||
heroConfig = session.get('heroConfig')
|
||||
heroConfig ?= me.get('heroConfig') if session is @session and not @headless
|
||||
heroConfig ?= {}
|
||||
|
@ -368,9 +332,8 @@ module.exports = class LevelLoader extends CocoClass
|
|||
@worldNecessities = (r for r in @worldNecessities when r?)
|
||||
@onWorldNecessitiesLoaded() if @checkAllWorldNecessitiesRegisteredAndLoaded()
|
||||
|
||||
onWorldNecessityLoadFailed: (event) ->
|
||||
@reportLoadError()
|
||||
@trigger('world-necessity-load-failed', event)
|
||||
onWorldNecessityLoadFailed: (resource) ->
|
||||
@trigger('world-necessity-load-failed', resource: resource)
|
||||
|
||||
checkAllWorldNecessitiesRegisteredAndLoaded: ->
|
||||
return false unless _.filter(@worldNecessities).length is 0
|
||||
|
@ -381,8 +344,6 @@ module.exports = class LevelLoader extends CocoClass
|
|||
|
||||
onWorldNecessitiesLoaded: ->
|
||||
console.log "World necessities loaded." if LOG
|
||||
return if @initialized
|
||||
@initialized = true
|
||||
@initWorld()
|
||||
@supermodel.clearMaxProgress()
|
||||
@trigger 'world-necessities-loaded'
|
||||
|
@ -410,7 +371,6 @@ module.exports = class LevelLoader extends CocoClass
|
|||
@supermodel.loadModel(model, resourceName)
|
||||
|
||||
onSupermodelLoaded: ->
|
||||
clearTimeout @loadTimeoutID
|
||||
return if @destroyed
|
||||
console.log 'SuperModel for Level loaded in', new Date().getTime() - @t0, 'ms' if LOG
|
||||
@loadLevelSounds()
|
||||
|
@ -441,8 +401,7 @@ module.exports = class LevelLoader extends CocoClass
|
|||
resource.markLoaded() if resource.spriteSheetKeys.length is 0
|
||||
|
||||
denormalizeSession: ->
|
||||
return if @sessionDenormalized or @spectateMode or @sessionless or me.isSessionless()
|
||||
return if @headless and not @level.isType('web-dev')
|
||||
return if @headless or @sessionDenormalized or @spectateMode or @sessionless or me.isSessionless()
|
||||
# This is a way (the way?) PUT /db/level.sessions/undefined was happening
|
||||
# See commit c242317d9
|
||||
return if not @session.id
|
||||
|
@ -484,7 +443,7 @@ module.exports = class LevelLoader extends CocoClass
|
|||
@grabTeamConfigs()
|
||||
@thangTypeTeams = {}
|
||||
for thang in @level.get('thangs')
|
||||
if @level.isType('hero', 'course') and thang.id is 'Hero Placeholder'
|
||||
if @level.get('type', true) in ['hero', 'course'] and thang.id is 'Hero Placeholder'
|
||||
continue # No team colors for heroes on single-player levels
|
||||
for component in thang.components
|
||||
if team = component.config?.team
|
||||
|
@ -510,7 +469,8 @@ module.exports = class LevelLoader extends CocoClass
|
|||
# World init
|
||||
|
||||
initWorld: ->
|
||||
return if @level.isType('web-dev')
|
||||
return if @initialized
|
||||
@initialized = true
|
||||
@world = new World()
|
||||
@world.levelSessionIDs = if @opponentSessionID then [@sessionID, @opponentSessionID] else [@sessionID]
|
||||
@world.submissionCount = @session?.get('state')?.submissionCount ? 0
|
||||
|
|
|
@ -74,7 +74,7 @@ module.exports = class LevelSetupManager extends CocoClass
|
|||
@session.set 'heroConfig', {"thangType":raider,"inventory":{}}
|
||||
@onInventoryModalPlayClicked()
|
||||
return
|
||||
if @level.isType('course', 'course-ladder', 'game-dev', 'web-dev') or window.serverConfig.picoCTF
|
||||
if @level.get('type', true) in ['course', 'course-ladder'] or window.serverConfig.picoCTF
|
||||
@onInventoryModalPlayClicked()
|
||||
return
|
||||
@heroesModal = new PlayHeroesModal({supermodel: @supermodel, session: @session, confirmButtonI18N: 'play.next', level: @level, hadEverChosenHero: @options.hadEverChosenHero})
|
||||
|
|
|
@ -6,10 +6,10 @@ mapred = (left, right, func) ->
|
|||
result or (_.reduce (_.map right, (singleRight) -> func(singleLeft, singleRight)),
|
||||
((intermediate, value) -> intermediate or value), false)), false)
|
||||
|
||||
doQuerySelector = (originalValue, operatorObj) ->
|
||||
value = if _.isArray originalValue then originalValue else [originalValue] # left hand can be an array too
|
||||
for operator, originalBody of operatorObj
|
||||
body = if _.isArray originalBody then originalBody else [originalBody] # right hand can be an array too
|
||||
doQuerySelector = (value, operatorObj) ->
|
||||
value = [value] unless _.isArray value # left hand can be an array too
|
||||
for operator, body of operatorObj
|
||||
body = [body] unless _.isArray body # right hand can be an array too
|
||||
switch operator
|
||||
when '$gt' then return false unless mapred value, body, (l, r) -> l > r
|
||||
when '$gte' then return false unless mapred value, body, (l, r) -> l >= r
|
||||
|
@ -19,9 +19,7 @@ doQuerySelector = (originalValue, operatorObj) ->
|
|||
when '$in' then return false unless _.reduce value, ((result, val) -> result or val in body), false
|
||||
when '$nin' then return false if _.reduce value, ((result, val) -> result or val in body), false
|
||||
when '$exists' then return false if value[0]? isnt body[0]
|
||||
else
|
||||
trimmedOperator = _.pick(operatorObj, operator)
|
||||
return false unless _.isObject(originalValue) and matchesQuery(originalValue, trimmedOperator)
|
||||
else return false
|
||||
true
|
||||
|
||||
matchesQuery = (target, queryObj) ->
|
||||
|
|
|
@ -4,7 +4,6 @@ module.exports =
|
|||
# Result: Each course instance gains a property, numCompleted, that is the
|
||||
# number of students in that course instance who have completed ALL of
|
||||
# the levels in thate course
|
||||
# TODO: simplify, classroom.sessions only includes sessions for assigned courses now
|
||||
calculateDots: (classrooms, courses, courseInstances) ->
|
||||
for classroom in classrooms.models
|
||||
# map [user, level] => session so we don't have to do find TODO
|
||||
|
@ -14,7 +13,6 @@ module.exports =
|
|||
instance.numCompleted = 0
|
||||
instance.started = false
|
||||
levels = classroom.getLevels({courseID: course.id})
|
||||
levels.remove(levels.filter((level) => level.get('practice')))
|
||||
for userID in instance.get('members')
|
||||
instance.started ||= _.any levels.models, (level) ->
|
||||
session = _.find classroom.sessions.models, (session) ->
|
||||
|
@ -47,7 +45,6 @@ module.exports =
|
|||
users = _.map userIDs, (id) ->
|
||||
students.get(id)
|
||||
return {
|
||||
courseName: course.get('name')
|
||||
courseNumber: courseIndex + 1
|
||||
levelNumber: levelIndex + 1
|
||||
levelName: level.get('name')
|
||||
|
@ -77,7 +74,6 @@ module.exports =
|
|||
users = _.map userIDs, (id) ->
|
||||
students.get(id)
|
||||
return {
|
||||
courseName: course.get('name')
|
||||
courseNumber: courseIndex + 1
|
||||
levelNumber: levelIndex + 1
|
||||
levelName: level.get('name')
|
||||
|
@ -182,7 +178,7 @@ module.exports =
|
|||
if _.find(sessions, (s) -> s.completed()) # have finished this level
|
||||
courseProgress.completed &&= true #no-op
|
||||
courseProgress[userID].completed &&= true #no-op
|
||||
courseProgress[userID].levelsCompleted += 1 unless level.get('practice')
|
||||
courseProgress[userID].levelsCompleted += 1
|
||||
courseProgress[levelID].completed &&= true #no-op
|
||||
# courseProgress[levelID].numCompleted += 1
|
||||
courseProgress[levelID][userID].completed = true
|
||||
|
@ -199,17 +195,6 @@ module.exports =
|
|||
_.assign(progressData, progressMixin)
|
||||
return progressData
|
||||
|
||||
courseLabelsArray: (courses) ->
|
||||
labels = []
|
||||
courseLabelIndexes = CS: 0, GD: 0, WD: 0
|
||||
for course in courses
|
||||
acronym = switch
|
||||
when /game-dev/.test(course.get('slug')) then 'GD'
|
||||
when /web-dev/.test(course.get('slug')) then 'WD'
|
||||
else 'CS'
|
||||
labels.push acronym + ++courseLabelIndexes[acronym]
|
||||
labels
|
||||
|
||||
progressMixin =
|
||||
get: (options={}) ->
|
||||
{ classroom, course, level, user } = options
|
||||
|
|
|
@ -114,6 +114,7 @@ module.exports = class Simulator extends CocoClass
|
|||
catch error
|
||||
console.log "Failed to form task results:", error
|
||||
return @cleanupAndSimulateAnotherTask()
|
||||
console.log 'Processing results:', taskResults
|
||||
humanSessionRank = taskResults.sessions[0].metrics.rank
|
||||
ogreSessionRank = taskResults.sessions[1].metrics.rank
|
||||
if @options.headlessClient and @options.simulateOnlyOneGame
|
||||
|
@ -376,25 +377,78 @@ module.exports = class Simulator extends CocoClass
|
|||
return 1
|
||||
|
||||
generateSpellsObject: ->
|
||||
spells = {}
|
||||
for {hero, team} in [{hero: 'Hero Placeholder', team: 'humans'}, {hero: 'Hero Placeholder 1', team: 'ogres'}]
|
||||
sessionInfo = _.filter(@task.getSessions(), {team: team})[0]
|
||||
fullSpellName = _.string.slugify(hero) + '/plan'
|
||||
submittedCodeLanguage = sessionInfo?.submittedCodeLanguage ? 'javascript'
|
||||
submittedCodeLanguage = 'javascript' if submittedCodeLanguage in ['clojure', 'io'] # No longer supported
|
||||
submittedCode = LZString.decompressFromUTF16 sessionInfo?.submittedCode?[_.string.slugify(hero)]?.plan ? ''
|
||||
aether = new Aether createAetherOptions functionName: 'plan', codeLanguage: submittedCodeLanguage, skipProtectAPI: false
|
||||
try
|
||||
aether.transpile submittedCode
|
||||
catch e
|
||||
console.log "Couldn't transpile #{fullSpellName}:\n#{submittedCode}\n", e
|
||||
aether.transpile ''
|
||||
spells[fullSpellName] = name: 'plan', team: team, thang: {thang: {id: hero}, aether: aether}
|
||||
spells
|
||||
@currentUserCodeMap = @task.generateSpellKeyToSourceMap()
|
||||
@spells = {}
|
||||
for thang in @level.attributes.thangs
|
||||
continue if @thangIsATemplate thang
|
||||
@generateSpellKeyToSourceMapPropertiesFromThang thang
|
||||
@spells
|
||||
|
||||
thangIsATemplate: (thang) ->
|
||||
for component in thang.components
|
||||
continue unless @componentHasProgrammableMethods component
|
||||
for methodName, method of component.config.programmableMethods
|
||||
return true if @methodBelongsToTemplateThang method
|
||||
|
||||
return false
|
||||
|
||||
componentHasProgrammableMethods: (component) -> component.config? and _.has component.config, 'programmableMethods'
|
||||
|
||||
methodBelongsToTemplateThang: (method) -> typeof method is 'string'
|
||||
|
||||
generateSpellKeyToSourceMapPropertiesFromThang: (thang) =>
|
||||
for component in thang.components
|
||||
continue unless @componentHasProgrammableMethods component
|
||||
for methodName, method of component.config.programmableMethods
|
||||
spellKey = @generateSpellKeyFromThangIDAndMethodName thang.id, methodName
|
||||
|
||||
@createSpellAndAssignName spellKey, methodName
|
||||
@createSpellThang thang, method, spellKey
|
||||
@transpileSpell thang, spellKey, methodName
|
||||
|
||||
generateSpellKeyFromThangIDAndMethodName: (thang, methodName) ->
|
||||
spellKeyComponents = [thang, methodName]
|
||||
spellKeyComponents[0] = _.string.slugify spellKeyComponents[0]
|
||||
spellKey = spellKeyComponents.join '/'
|
||||
spellKey
|
||||
|
||||
createSpellAndAssignName: (spellKey, spellName) ->
|
||||
@spells[spellKey] ?= {}
|
||||
@spells[spellKey].name = spellName
|
||||
|
||||
createSpellThang: (thang, method, spellKey) ->
|
||||
@spells[spellKey].thangs ?= {}
|
||||
@spells[spellKey].thangs[thang.id] ?= {}
|
||||
spellTeam = @task.getSpellKeyToTeamMap()[spellKey]
|
||||
playerTeams = @task.getPlayerTeams()
|
||||
useProtectAPI = true
|
||||
if spellTeam not in playerTeams
|
||||
useProtectAPI = false
|
||||
else
|
||||
spellSession = _.filter(@task.getSessions(), {team: spellTeam})[0]
|
||||
unless codeLanguage = spellSession?.submittedCodeLanguage
|
||||
console.warn 'Session', spellSession.creatorName, spellSession.team, 'didn\'t have submittedCodeLanguage, just:', spellSession
|
||||
@spells[spellKey].thangs[thang.id].aether = @createAether @spells[spellKey].name, method, useProtectAPI, codeLanguage ? 'javascript'
|
||||
|
||||
transpileSpell: (thang, spellKey, methodName) ->
|
||||
slugifiedThangID = _.string.slugify thang.id
|
||||
generatedSpellKey = [slugifiedThangID,methodName].join '/'
|
||||
source = @currentUserCodeMap[generatedSpellKey] ? ''
|
||||
aether = @spells[spellKey].thangs[thang.id].aether
|
||||
#unless _.contains(@task.spellKeysToTranspile, generatedSpellKey)
|
||||
try
|
||||
aether.transpile source
|
||||
catch e
|
||||
console.log "Couldn't transpile #{spellKey}:\n#{source}\n", e
|
||||
aether.transpile ''
|
||||
|
||||
createAether: (methodName, method, useProtectAPI, codeLanguage) ->
|
||||
aetherOptions = createAetherOptions functionName: methodName, codeLanguage: codeLanguage, skipProtectAPI: not useProtectAPI
|
||||
return new Aether aetherOptions
|
||||
|
||||
class SimulationTask
|
||||
constructor: (@rawData) ->
|
||||
@spellKeyToTeamMap = {}
|
||||
|
||||
getLevelName: ->
|
||||
levelName = @rawData.sessions?[0]?.levelID
|
||||
|
@ -422,4 +476,30 @@ class SimulationTask
|
|||
|
||||
getSessions: -> @rawData.sessions
|
||||
|
||||
getSpellKeyToTeamMap: -> @spellKeyToTeamMap
|
||||
|
||||
getPlayerTeams: -> _.pluck @rawData.sessions, 'team'
|
||||
|
||||
setWorld: (@world) ->
|
||||
|
||||
generateSpellKeyToSourceMap: ->
|
||||
# TODO: we always now only have hero-placeholder/plan vs. hero-placeholder-1/plan on humans vs. ogres, always just have to retranspile for Esper, and never need to transpile for NPCs or other methods, so we can get rid of almost all of this stuff.
|
||||
playerTeams = _.pluck @rawData.sessions, 'team'
|
||||
spellKeyToSourceMap = {}
|
||||
for session in @rawData.sessions
|
||||
teamSpells = session.teamSpells[session.team]
|
||||
allTeams = _.keys session.teamSpells
|
||||
for team in allTeams
|
||||
for spell in session.teamSpells[team]
|
||||
@spellKeyToTeamMap[spell] = team
|
||||
teamCode = {}
|
||||
|
||||
for thangName, thangSpells of session.submittedCode
|
||||
for spellName, spell of thangSpells
|
||||
fullSpellName = [thangName, spellName].join '/'
|
||||
if _.contains(teamSpells, fullSpellName)
|
||||
teamCode[fullSpellName] = LZString.decompressFromUTF16 spell
|
||||
|
||||
_.merge spellKeyToSourceMap, teamCode
|
||||
|
||||
spellKeyToSourceMap
|
||||
|
|
|
@ -358,9 +358,6 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass
|
|||
prerenderedSpriteSheet = thangType.getPrerenderedSpriteSheet(colorConfig, 'segmented')
|
||||
if prerenderedSpriteSheet and not prerenderedSpriteSheet.loadedImage
|
||||
return
|
||||
else if prerenderedSpriteSheet
|
||||
animations = prerenderedSpriteSheet.spriteSheet._animations
|
||||
renderedActions = _.zipObject(animations, _.times(animations.length, -> true))
|
||||
containersToRender = thangType.getContainersForActions(actionNames)
|
||||
#console.log 'render segmented', thangType.get('name'), actionNames, colorConfig, 'because we do not have prerendered sprite sheet?', prerenderedSpriteSheet
|
||||
spriteBuilder = new SpriteBuilder(thangType, {colorConfig: colorConfig})
|
||||
|
@ -370,7 +367,7 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass
|
|||
container = new createjs.Sprite(@spriteSheet)
|
||||
container.gotoAndStop(containerKey)
|
||||
frame = spriteSheetBuilder.addFrame(container)
|
||||
else if prerenderedSpriteSheet and renderedActions[containerGlobalName]
|
||||
else if prerenderedSpriteSheet
|
||||
container = new createjs.Sprite(prerenderedSpriteSheet.spriteSheet)
|
||||
container.gotoAndStop(containerGlobalName)
|
||||
scale = @resolutionFactor / (prerenderedSpriteSheet.get('resolutionFactor') or 1)
|
||||
|
|
|
@ -10,6 +10,7 @@ Letterbox = require './Letterbox'
|
|||
Dimmer = require './Dimmer'
|
||||
CountdownScreen = require './CountdownScreen'
|
||||
PlaybackOverScreen = require './PlaybackOverScreen'
|
||||
WaitingScreen = require './WaitingScreen'
|
||||
DebugDisplay = require './DebugDisplay'
|
||||
CoordinateDisplay = require './CoordinateDisplay'
|
||||
CoordinateGrid = require './CoordinateGrid'
|
||||
|
@ -69,6 +70,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
'level:set-letterbox': 'onSetLetterbox'
|
||||
'application:idle-changed': 'onIdleChanged'
|
||||
'camera:zoom-updated': 'onZoomUpdated'
|
||||
'playback:real-time-playback-waiting': 'onRealTimePlaybackWaiting'
|
||||
'playback:real-time-playback-started': 'onRealTimePlaybackStarted'
|
||||
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
|
||||
'level:flag-color-selected': 'onFlagColorSelected'
|
||||
|
@ -83,8 +85,6 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
|
||||
constructor: (@world, @normalCanvas, @webGLCanvas, givenOptions) ->
|
||||
super()
|
||||
$(window).on('keydown', @onKeyEvent)
|
||||
$(window).on('keyup', @onKeyEvent)
|
||||
@normalLayers = []
|
||||
@options = _.clone(@defaults)
|
||||
@options = _.extend(@options, givenOptions) if givenOptions
|
||||
|
@ -94,9 +94,9 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
})
|
||||
@realTimeInputEvents = @gameUIState.get('realTimeInputEvents')
|
||||
@listenTo(@gameUIState, 'sprite:mouse-down', @onSpriteMouseDown)
|
||||
@onResize = _.debounce @onResize, resizeDelay
|
||||
@initEasel()
|
||||
@initAudio()
|
||||
@onResize = _.debounce @onResize, resizeDelay
|
||||
$(window).on 'resize', @onResize
|
||||
if @world.ended
|
||||
_.defer => @setWorld @world
|
||||
|
@ -133,9 +133,9 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@handleEvents
|
||||
})
|
||||
@countdownScreen = new CountdownScreen camera: @camera, layer: @screenLayer, showsCountdown: @world.showsCountdown
|
||||
unless @options.levelType is 'game-dev'
|
||||
@playbackOverScreen = new PlaybackOverScreen camera: @camera, layer: @screenLayer, playerNames: @options.playerNames
|
||||
@normalStage.addChildAt @playbackOverScreen.dimLayer, 0 # Put this below the other layers, actually, so we can more easily read text on the screen.
|
||||
@playbackOverScreen = new PlaybackOverScreen camera: @camera, layer: @screenLayer, playerNames: @options.playerNames
|
||||
@normalStage.addChildAt @playbackOverScreen.dimLayer, 0 # Put this below the other layers, actually, so we can more easily read text on the screen.
|
||||
@waitingScreen = new WaitingScreen camera: @camera, layer: @screenLayer
|
||||
@initCoordinates()
|
||||
@webGLStage.enableMouseOver(10)
|
||||
@webGLStage.addEventListener 'stagemousemove', @onMouseMove
|
||||
|
@ -230,7 +230,6 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
|
||||
restoreWorldState: ->
|
||||
frame = @world.getFrame(@getCurrentFrame())
|
||||
return unless frame
|
||||
frame.restoreState()
|
||||
current = Math.max(0, Math.min(@currentFrame, @world.frames.length - 1))
|
||||
if current - Math.floor(current) > 0.01 and Math.ceil(current) < @world.frames.length - 1
|
||||
|
@ -329,12 +328,12 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@surfacePauseTimeout = _.delay performToggle, 2000
|
||||
@lankBoss.stop()
|
||||
@trailmaster?.stop()
|
||||
@playbackOverScreen?.show()
|
||||
@playbackOverScreen.show()
|
||||
else
|
||||
performToggle()
|
||||
@lankBoss.play()
|
||||
@trailmaster?.play()
|
||||
@playbackOverScreen?.hide()
|
||||
@playbackOverScreen.hide()
|
||||
|
||||
|
||||
|
||||
|
@ -352,7 +351,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
world: @world
|
||||
)
|
||||
|
||||
if (not @world.indefiniteLength) and @lastFrame < @world.frames.length and @currentFrame >= @world.totalFrames - 1
|
||||
if @lastFrame < @world.frames.length and @currentFrame >= @world.totalFrames - 1
|
||||
@ended = true
|
||||
@setPaused true
|
||||
Backbone.Mediator.publish 'surface:playback-ended', {}
|
||||
|
@ -542,15 +541,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
event.screenPos = @mouseScreenPos if @mouseScreenPos
|
||||
Backbone.Mediator.publish 'surface:mouse-scrolled', event unless @disabled
|
||||
@gameUIState.trigger('surface:mouse-scrolled', event)
|
||||
|
||||
|
||||
#- Keyboard callbacks
|
||||
|
||||
onKeyEvent: (e) =>
|
||||
return unless @realTime
|
||||
event = _.pick(e, 'type', 'keyCode', 'ctrlKey', 'metaKey', 'shiftKey')
|
||||
event.time = @world.dt * @world.frames.length
|
||||
@realTimeInputEvents.add(event)
|
||||
|
||||
|
||||
#- Canvas callbacks
|
||||
|
||||
|
@ -563,9 +554,6 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
if application.isIPadApp
|
||||
newWidth = 1024
|
||||
newHeight = newWidth / aspectRatio
|
||||
else if @options.resizeStrategy is 'wrapper-size'
|
||||
newWidth = $('#canvas-wrapper').width()
|
||||
newHeight = newWidth / aspectRatio
|
||||
else if @realTime or @options.spectateGame
|
||||
pageHeight = $('#page-container').height() - $('#control-bar-view').outerHeight() - $('#playback-view').outerHeight()
|
||||
newWidth = Math.min pageWidth, pageHeight * aspectRatio
|
||||
|
@ -582,7 +570,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
scaleFactor = 1
|
||||
if @options.stayVisible
|
||||
availableHeight = window.innerHeight
|
||||
availableHeight -= $('.ad-container').outerHeight()
|
||||
availableHeight -= $('.ad-container').outerHeight()
|
||||
availableHeight -= $('#game-area').outerHeight() - $('#canvas-wrapper').outerHeight()
|
||||
scaleFactor = availableHeight / newHeight if availableHeight < newHeight
|
||||
newWidth *= scaleFactor
|
||||
|
@ -591,7 +579,6 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
return if newWidth is oldWidth and newHeight is oldHeight and not @options.spectateGame
|
||||
return if newWidth < 200 or newHeight < 200
|
||||
@normalCanvas.add(@webGLCanvas).attr width: newWidth, height: newHeight
|
||||
@trigger 'resize', { width: newWidth, height: newHeight }
|
||||
|
||||
# Cannot do this to the webGLStage because it does not use scaleX/Y.
|
||||
# Instead the LayerAdapter scales webGL-enabled layers.
|
||||
|
@ -615,6 +602,9 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
|
||||
#- Real-time playback
|
||||
|
||||
onRealTimePlaybackWaiting: (e) ->
|
||||
@onRealTimePlaybackStarted e
|
||||
|
||||
onRealTimePlaybackStarted: (e) ->
|
||||
return if @realTime
|
||||
@realTimeInputEvents.reset()
|
||||
|
@ -751,6 +741,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@dimmer?.destroy()
|
||||
@countdownScreen?.destroy()
|
||||
@playbackOverScreen?.destroy()
|
||||
@waitingScreen?.destroy()
|
||||
@coordinateDisplay?.destroy()
|
||||
@coordinateGrid?.destroy()
|
||||
@normalStage.clear()
|
||||
|
@ -768,8 +759,6 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@webGLStage.enableMouseOver 0
|
||||
@webGLCanvas.off 'mousewheel', @onMouseWheel
|
||||
$(window).off 'resize', @onResize
|
||||
$(window).off('keydown', @onKeyEvent)
|
||||
$(window).off('keyup', @onKeyEvent)
|
||||
clearTimeout @surfacePauseTimeout if @surfacePauseTimeout
|
||||
clearTimeout @surfaceZoomPauseTimeout if @surfaceZoomPauseTimeout
|
||||
super()
|
||||
|
|
65
app/lib/surface/WaitingScreen.coffee
Normal file
|
@ -0,0 +1,65 @@
|
|||
CocoClass = require 'core/CocoClass'
|
||||
RealTimeCollection = require 'collections/RealTimeCollection'
|
||||
|
||||
module.exports = class WaitingScreen extends CocoClass
|
||||
subscriptions:
|
||||
'playback:real-time-playback-waiting': 'onRealTimePlaybackWaiting'
|
||||
'playback:real-time-playback-started': 'onRealTimePlaybackStarted'
|
||||
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
|
||||
'real-time-multiplayer:player-status': 'onRealTimeMultiplayerPlayerStatus'
|
||||
|
||||
constructor: (options) ->
|
||||
super()
|
||||
options ?= {}
|
||||
@camera = options.camera
|
||||
@layer = options.layer
|
||||
@waitingText = options.text or 'Waiting...'
|
||||
console.error @toString(), 'needs a camera.' unless @camera
|
||||
console.error @toString(), 'needs a layer.' unless @layer
|
||||
@build()
|
||||
|
||||
onCastingBegins: (e) -> @show() unless e.preload
|
||||
onCastingEnds: (e) -> @hide()
|
||||
|
||||
toString: -> '<WaitingScreen>'
|
||||
|
||||
build: ->
|
||||
@dimLayer = new createjs.Container()
|
||||
@dimLayer.mouseEnabled = @dimLayer.mouseChildren = false
|
||||
@dimLayer.addChild @dimScreen = new createjs.Shape()
|
||||
@dimScreen.graphics.beginFill('rgba(0,0,0,0.5)').rect 0, 0, @camera.canvasWidth, @camera.canvasHeight
|
||||
@dimLayer.alpha = 0
|
||||
@dimLayer.addChild @makeWaitingText()
|
||||
|
||||
makeWaitingText: ->
|
||||
size = Math.ceil @camera.canvasHeight / 8
|
||||
text = new createjs.Text @waitingText, "#{size}px Open Sans Condensed", '#F7B42C'
|
||||
text.shadow = new createjs.Shadow '#000', Math.ceil(@camera.canvasHeight / 300), Math.ceil(@camera.canvasHeight / 300), Math.ceil(@camera.canvasHeight / 120)
|
||||
text.textAlign = 'center'
|
||||
text.textBaseline = 'middle'
|
||||
text.x = @camera.canvasWidth / 2
|
||||
text.y = @camera.canvasHeight / 2
|
||||
@text = text
|
||||
return text
|
||||
|
||||
show: ->
|
||||
return if @showing
|
||||
@showing = true
|
||||
@dimLayer.alpha = 0
|
||||
createjs.Tween.removeTweens @dimLayer
|
||||
createjs.Tween.get(@dimLayer).to({alpha: 1}, 500)
|
||||
@layer.addChild @dimLayer
|
||||
|
||||
hide: ->
|
||||
return unless @showing
|
||||
@showing = false
|
||||
createjs.Tween.removeTweens @dimLayer
|
||||
createjs.Tween.get(@dimLayer).to({alpha: 0}, 500).call => @layer.removeChild @dimLayer unless @destroyed
|
||||
|
||||
onRealTimeMultiplayerPlayerStatus: (e) -> @text.text = e.status
|
||||
|
||||
onRealTimePlaybackWaiting: (e) -> @show()
|
||||
|
||||
onRealTimePlaybackStarted: (e) -> @hide()
|
||||
|
||||
onRealTimePlaybackEnded: (e) -> @hide()
|
|
@ -38,7 +38,6 @@ module.exports = class GoalManager extends CocoClass
|
|||
|
||||
subscriptions:
|
||||
'god:new-world-created': 'onNewWorldCreated'
|
||||
'god:new-html-goal-states': 'onNewHTMLGoalStates'
|
||||
'level:restarted': 'onLevelRestarted'
|
||||
|
||||
backgroundSubscriptions:
|
||||
|
@ -87,9 +86,6 @@ module.exports = class GoalManager extends CocoClass
|
|||
@world = e.world
|
||||
@updateGoalStates(e.goalStates) if e.goalStates?
|
||||
|
||||
onNewHTMLGoalStates: (e) ->
|
||||
@updateGoalStates(e.goalStates) if e.goalStates?
|
||||
|
||||
updateGoalStates: (newGoalStates) ->
|
||||
for goalID, goalState of newGoalStates
|
||||
continue unless @goalStates[goalID]?
|
||||
|
@ -118,7 +114,7 @@ module.exports = class GoalManager extends CocoClass
|
|||
goalStates: @goalStates
|
||||
goals: @goals
|
||||
overallStatus: overallStatus
|
||||
timedOut: @world? and (@world.totalFrames is @world.maxTotalFrames and overallStatus not in ['success', 'failure'])
|
||||
timedOut: @world.totalFrames is @world.maxTotalFrames and overallStatus not in ['success', 'failure']
|
||||
Backbone.Mediator.publish('goal-manager:new-goal-states', event)
|
||||
|
||||
checkOverallStatus: (ignoreIncomplete=false) ->
|
||||
|
@ -268,7 +264,7 @@ module.exports = class GoalManager extends CocoClass
|
|||
mostEagerGoal = _.min matchedGoals, 'worldEndsAfter'
|
||||
victory = overallStatus is 'success'
|
||||
tentative = overallStatus is 'success'
|
||||
@world?.endWorld victory, mostEagerGoal.worldEndsAfter, tentative if mostEagerGoal isnt Infinity
|
||||
@world.endWorld victory, mostEagerGoal.worldEndsAfter, tentative if mostEagerGoal isnt Infinity
|
||||
|
||||
updateGoalState: (goalID, thangID, progressObjectName, frameNumber) ->
|
||||
# A thang has done something related to the goal!
|
||||
|
@ -295,7 +291,7 @@ module.exports = class GoalManager extends CocoClass
|
|||
mostEagerGoal = _.min matchedGoals, 'worldEndsAfter'
|
||||
victory = overallStatus is 'success'
|
||||
tentative = overallStatus is 'success'
|
||||
@world?.endWorld victory, mostEagerGoal.worldEndsAfter, tentative if mostEagerGoal isnt Infinity
|
||||
@world.endWorld victory, mostEagerGoal.worldEndsAfter, tentative if mostEagerGoal isnt Infinity
|
||||
|
||||
goalIsPositive: (goalID) ->
|
||||
# Positive goals are completed when all conditions are true (kill all these thangs)
|
||||
|
|
|
@ -155,7 +155,7 @@ module.exports.thangNames = thangNames =
|
|||
'Frog Pet': ['Hypnotoad']
|
||||
'Griffin Pet': ['']
|
||||
'Pugicorn Pet': ['']
|
||||
'Polar Bear Pet': ['Klondike']
|
||||
'Polar Bear Pet': ['']
|
||||
'Wolf Pet': ['']
|
||||
'Horse': [
|
||||
# Animal
|
||||
|
|
|
@ -28,7 +28,6 @@ module.exports = class World
|
|||
debugging: false # Whether we are just rerunning to debug a world we've already cast
|
||||
headless: false # Whether we are just simulating for goal states instead of all serialized results
|
||||
framesSerializedSoFar: 0
|
||||
framesClearedSoFar: 0
|
||||
apiProperties: ['age', 'dt']
|
||||
realTimeBufferMax: REAL_TIME_BUFFER_MAX / 1000
|
||||
constructor: (@userCodeMap, classMap) ->
|
||||
|
@ -97,7 +96,6 @@ module.exports = class World
|
|||
|
||||
loadFrames: (loadedCallback, errorCallback, loadProgressCallback, preloadedCallback, skipDeferredLoading, loadUntilFrame) ->
|
||||
return if @aborted
|
||||
@totalFrames = 2 if @justBegin
|
||||
console.log 'Warning: loadFrames called on empty World (no thangs).' unless @thangs.length
|
||||
continueLaterFn = =>
|
||||
@loadFrames(loadedCallback, errorCallback, loadProgressCallback, preloadedCallback, skipDeferredLoading, loadUntilFrame) unless @destroyed
|
||||
|
@ -118,13 +116,7 @@ module.exports = class World
|
|||
@lastRealTimeUpdate ?= 0
|
||||
frameToLoadUntil = if loadUntilFrame then loadUntilFrame + 1 else @totalFrames # Might stop early if debugging.
|
||||
i = @frames.length
|
||||
while true
|
||||
if @indefiniteLength
|
||||
break if not @realTime # realtime has been stopped
|
||||
break if @victory? # game won or lost # TODO: give a couple seconds of buffer after victory is set instead of ending instantly
|
||||
else
|
||||
break if i >= frameToLoadUntil
|
||||
break if i >= @totalFrames
|
||||
while i < frameToLoadUntil and i < @totalFrames
|
||||
return unless @shouldContinueLoading t1, loadProgressCallback, skipDeferredLoading, continueLaterFn
|
||||
@adjustFlowSettings loadUntilFrame if @debugging
|
||||
try
|
||||
|
@ -387,11 +379,6 @@ module.exports = class World
|
|||
@freeMemoryBeforeFinalSerialization() if @ended
|
||||
startFrame = @framesSerializedSoFar
|
||||
endFrame = @frames.length
|
||||
if @indefiniteLength
|
||||
toClear = Math.max(@framesSerializedSoFar-10, 0)
|
||||
for i in _.range(@framesClearedSoFar, toClear)
|
||||
@frames[i] = null
|
||||
@framesClearedSoFar = @framesSerializedSoFar
|
||||
#console.log "... world serializing frames from", startFrame, "to", endFrame, "of", @totalFrames
|
||||
[transferableObjects, nontransferableObjects] = [0, 0]
|
||||
serializedFlagHistory = (_.omit(_.clone(flag), 'processed') for flag in @flagHistory)
|
||||
|
@ -538,14 +525,6 @@ module.exports = class World
|
|||
perf.framesCPUTime = 0
|
||||
w.frames = [] unless streamingWorld
|
||||
clearTimeout @deserializationTimeout if @deserializationTimeout
|
||||
|
||||
if w.indefiniteLength
|
||||
clearTo = Math.max(w.frames.length - 100, 0)
|
||||
if clearTo > w.framesClearedSoFar
|
||||
for i in _.range(w.framesClearedSoFar, clearTo)
|
||||
w.frames[i] = null
|
||||
w.framesClearedSoFar = clearTo
|
||||
|
||||
@deserializationTimeout = _.delay @deserializeSomeFrames, 1, o, w, finishedWorldCallback, perf, startFrame, endFrame
|
||||
w # Return in-progress deserializing world
|
||||
|
||||
|
@ -609,7 +588,6 @@ module.exports = class World
|
|||
lastPos = x: null, y: null
|
||||
for frameIndex in [lastFrameIndex .. 0] by -1
|
||||
frame = @frames[frameIndex]
|
||||
continue unless frame # may have been evicted for game dev levels
|
||||
if pos = frame.thangStateMap[thangID]?.getStateForProp 'pos'
|
||||
pos = camera.worldToSurface {x: pos.x, y: pos.y} if camera # without z
|
||||
if not lastPos.x? or (Math.abs(lastPos.x - pos.x) + Math.abs(lastPos.y - pos.y)) > 1
|
||||
|
|
|
@ -10,7 +10,7 @@ module.exports = class WorldFrame
|
|||
getNextFrame: ->
|
||||
# Optimized. Must be called while thangs are current at this frame.
|
||||
nextTime = @time + @world.dt
|
||||
return null if nextTime > @world.lifespan and not @world.indefiniteLength
|
||||
return null if nextTime > @world.lifespan
|
||||
@hash = @world.rand.seed
|
||||
@hash += system.update() for system in @world.systems
|
||||
nextFrame = new WorldFrame(@world, nextTime)
|
||||
|
|