Merge branch 'master' into production

This commit is contained in:
Nick Winter 2016-07-27 16:37:30 -07:00
commit 76113fd5ab
9 changed files with 327 additions and 97 deletions

View file

@ -51,6 +51,7 @@ 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')
'beta': go('HomeView')

View file

@ -114,7 +114,6 @@ 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
@ -377,78 +376,24 @@ module.exports = class Simulator extends CocoClass
return 1
generateSpellsObject: ->
@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)
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'
submittedCode = LZString.decompressFromUTF16 sessionInfo?.submittedCode?[_.string.slugify(hero)]?.plan ? ''
aether = new Aether createAetherOptions functionName: 'plan', codeLanguage: submittedCodeLanguage, skipProtectAPI: false
try
aether.transpile source
aether.transpile submittedCode
catch e
console.log "Couldn't transpile #{spellKey}:\n#{source}\n", 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
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
@ -476,30 +421,4 @@ 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

View file

@ -798,6 +798,7 @@
phoenix_title: "Software Engineer"
nolan_title: "Territory Manager"
elliot_title: "Partnership Manager"
elliot_blurb: "Mindreader"
lisa_title: "Market Development Rep"
retrostyle_title: "Illustration"
retrostyle_blurb: "RetroStyle Games"

View file

@ -137,7 +137,7 @@ block content
.team-bio
h6.label.team-name Elliot Okiwelu
small(data-i18n="about.elliot_title")
br
small(data-i18n="about.elliot_blurb")
li
img(src="/images/pages/about/lisa_small.png").img-thumbnail

View file

@ -11,3 +11,6 @@ block content
div
a(href="/artisans/solution-problems")
|Solution Problems
div
a(href="/artisans/level-concepts")
|Level Concept Map

View file

@ -0,0 +1,24 @@
// DNT
extends /templates/base
block content
#solution-problems-view
div
a(href='/artisans')
span.glyphicon.glyphicon-chevron-left
span Artisans Home
br
for course, k in (view.data || {})
h3= k
table.table.table-striped
tr
th No
th Level Name
th Concepts Detected
for level in course
tr
td= level.seqNo
td(style="width:10%")= level.get('name')
td
for tag in (level.tags || [])
span.label.label-primary(style='margin-right: 10px; float: left; line-height: 20px; margin-bottom: 10px')= tag

View file

@ -0,0 +1,173 @@
RootView = require 'views/core/RootView'
template = require 'templates/artisans/concept-map-view'
Level = require 'models/Level'
Campaign = require 'models/Campaign'
CocoCollection = require 'collections/CocoCollection'
Campaigns = require 'collections/Campaigns'
Levels = require 'collections/Levels'
parser = new esper().realm.parser
module.exports = class LevelConceptMap extends RootView
template: template
id: 'solution-problems-view'
excludedCampaigns = [
# Misc. campaigns
'picoctf', 'auditions'
# Campaign-version campaigns
#'dungeon', 'forest', 'desert', 'mountain', 'glacier'
# Test campaigns
'dungeon-branching-test', 'forest-branching-test', 'desert-branching-test'
# Course-version campaigns
#'intro', 'course-2', 'course-3', 'course-4', 'course-5', 'course-6'
]
includedLanguages = [
'javascript'
]
excludedLevelSnippets = [
'treasure', 'brawl', 'siege'
]
unloadedCampaigns: 0
campaignLevels: {}
loadedLevels: {}
data: {}
problemCount: 0
initialize: ->
@campaigns = new Campaigns([])
@listenTo(@campaigns, 'sync', @onCampaignsLoaded)
@supermodel.trackRequest(@campaigns.fetch(
data:
project:'slug'
))
onCampaignsLoaded: (campCollection) ->
for campaign in campCollection.models
campaignSlug = campaign.get('slug')
continue if campaignSlug in excludedCampaigns
@unloadedCampaigns++
@campaignLevels[campaignSlug] = new Levels()
@listenTo(@campaignLevels[campaignSlug], 'sync', @onLevelsLoaded.bind @, campaignSlug)
@supermodel.trackRequest(@campaignLevels[campaignSlug].fetchForCampaign(campaignSlug,
data:
project: 'thangs,name,slug,campaign'
))
onLevelsLoaded: (campaignSlug, lvlCollection) ->
for level, k in lvlCollection.models
level.campaign = campaignSlug
@loadedLevels[campaignSlug] = {} unless @loadedLevels[campaignSlug]?
ll = {} unless ll?
level.seqNo = lvlCollection.models.length - k
@loadedLevels[campaignSlug][level.get('slug')] = level
if --@unloadedCampaigns is 0
@onAllLevelsLoaded()
onAllLevelsLoaded: ->
for campaignSlug, campaign of @loadedLevels
for levelSlug, level of campaign
unless level?
console.error 'Level Slug doesn\'t have associated Level', levelSlug
continue
isBad = false
for word in excludedLevelSnippets
if levelSlug.indexOf(word) isnt -1
isBad = true
continue if isBad
thangs = level.get 'thangs'
component = null
thangs = _.filter(thangs, (elem) ->
return _.findWhere(elem.components, (elem2) ->
if elem2.config?.programmableMethods?
component = elem2
return true
)
)
if thangs.length > 1
console.warn 'Level has more than 1 programmableMethod Thangs', levelSlug
continue
unless component?
console.error 'Level doesn\'t have programmableMethod Thang', levelSlug
continue
plan = component.config.programmableMethods.plan
level.tags = @tagLevel _.find plan.solutions, (s) -> s.language is 'javascript'
@data[campaignSlug] = _.sortBy _.values(@loadedLevels[campaignSlug]), 'seqNo'
console.log @render, @loadedLevels
@render()
tagLevel: (src) ->
return [] if not src?.source?
try
ast = parser(src.source)
catch e
return ['parse error: ' + e.message]
tags = {}
process = (n) ->
return unless n?
switch n.type
when "Program", "BlockStatement"
process(n) for n in n.body
when "FunctionDeclaration"
tags['function-def'] = true
if n.params > 0
tags['function-params:' + n.params.length] = true
process(n.body)
when "ExpressionStatement"
process(n.expression)
when "CallExpression"
process(n.callee)
when "MemberExpression"
if n.object?.name is 'hero'
tags["hero." + n.property.name] = true
when "WhileStatement"
if n.test.type is 'Literal' and n.test.value is true
tags['while-true'] = true
else
tags['while'] = true
process(n.test)
process(n.body)
when "ForStatement"
tags['for'] = true
process(n.init)
process(n.test)
process(n.update)
process(n.body)
when "IfStatement"
tags['if'] = true
process(n.test)
process(n.consequent)
process(n.alternate)
when "Literal"
if n.value is true
tags['true'] = true
else
tags['literal:' + typeof n.value] = true
when "BinaryExpression","LogicalExpression"
process(n.left)
process(n.right)
tags[n.operator] = true
when "AssignmentExpression"
tags['assign:' + n.operator] = true
process(n.right)
else
tags[n.type] = true
process ast
Object.keys(tags)

View file

@ -30,6 +30,7 @@ module.exports = class RootView extends CocoView
'click a': 'onClickAnchor'
'click button': 'toggleModal'
'click li': 'toggleModal'
'treema-error': 'onTreemaError'
subscriptions:
'achievements:new': 'handleNewAchievements'
@ -184,3 +185,6 @@ module.exports = class RootView extends CocoView
navigateToAdmin: ->
if window.amActually or me.isAdmin()
application.router.navigate('/admin', {trigger: true})
onTreemaError: (e) ->
noty text: e.message, layout: 'topCenter', type: 'error', killer: false, timeout: 5000, dismissQueue: true

View file

@ -0,0 +1,105 @@
if ( typeof password === 'undefined' ) {
throw "Please specify the coco user password on the commandline as --eval 'password \"<password>\"'";
}
if ( typeof _ === 'undefined' ) {
throw "Please include underscore/lodash on the commandline before this script.";
}
var main = connect('ec2-52-4-223-77.compute-1.amazonaws.com:27017/coco', 'coco', password);
var ls = connect('ec2-54-236-64-198.compute-1.amazonaws.com:27017/coco', 'coco', password);
var users = main.users.find(
{role: 'student',
//birthday: {$regex: /^20/}
birthday: {$exists: 1}
},
{_id: 1, birthday: 1, email: 1}
).toArray();
print("Found " + users.length + " users in age range");
var names = {
"5411cb3769152f1707be029c": "Dungeons of Kithgard",
"54173c90844506ae0195a0b4": "Gems in the Deep",
"54174347844506ae0195a0b8": "Shadow Guard",
"54ca592de4983255055a5478": "Enemy Mine",
"541875da4c16460000ab990f": "True Names",
"5604169b60537b8705386a59": "Kithgard Librarian",
"55ca293b9bc1892c835b0136": "Fire Dancing",
"565ce2291b940587057366dd": "Loop Da Loop",
"545a5914d820eb0000f6dc0a": "Haunted Kithmaze",
"5418cf256bae62f707c7e1c3": "The Second Kithmaze",
"5418d40f4c16460000ab9ac2": "Dread Door",
"54e0cdefe308cb510555a7f5": "Cupboards of Kithgard",
"54f0e074a375e47f055d619c": "Breakout",
"5452adea57e83800009730ee": "Known Enemy",
"5452c3ce57e83800009730f7": "Master of Names",
"55ca29439bc1892c835b0137": "A Mayhem of Munchkins",
"5452d8b906a59e000067e4fa": "The Gauntlet",
"541b434e1ccc8eaae19f3c33": "The Final Kithmaze",
"541c9a30c6362edfb0f34479": "Kithgard Gates",
"5630eab0c0fcbd86057cc2f8": "Wakka Maul"
};
var samples = 0;
var excluded = 0;
var buckets = {
30: 0,
45: 0,
60: 0,
75: 0,
90: 0,
120: 0,
'Infinity': 0
};
var years = {
};
_.shuffle(users.slice(0,1000)).forEach(function(user, idx) {
print("Scan " + user.email + ' / ' + user.birthday);
var totalPlayTime = 0;
var sessions = ls.level.sessions.find(
{code: {$exists: 1}, creator: user._id.valueOf()},
{'created': 1, 'level': 1, playtime: 1, 'state.complete': 1}
).toArray();
sessions = _.sortBy(sessions, 'created');
var success = false;
for ( var i = 0; i < sessions.length; ++i ) {
var s = sessions[i];
var name = names[s.level.original];
if ( !name ) {
++excluded;
return;
}
totalPlayTime += s.playtime;
if ( name == 'Known Enemy' && s.state.complete ) {
success = true;
break;
}
//print(s.created, name, s.state.complete, totalPlayTime / 60);
}
if ( !success && totalPlayTime < 60 * 90 ) return;
var by = ISODate(user.birthday).getYear();
if ( !years[by] ) years[by] = 1;
else ++years[by];
++samples;
if ( success ) {
for ( var bracket in buckets ) {
if ( Number(bracket) * 60 >= totalPlayTime ) {
buckets[bracket]++;
}
}
}
//print(JSON.stringify(sessions, null, ' '));
});
print("Excluded " + excluded + "\tSample Size:" + samples );
_.forEach(buckets, function(v,k) {
print(k + 'm\t', (v * 100 / samples) + '%');
});
//print(JSON.stringify(years, null, ' '));