From 2cfe7d0b430c5d27fd7aaef95acc2efb6253544b Mon Sep 17 00:00:00 2001 From: Robin Yang Date: Tue, 26 Jul 2016 15:32:37 -0700 Subject: [PATCH 1/6] Add Elliot Blurb on About Page --- app/locale/en.coffee | 1 + app/templates/about.jade | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 9aee76dfa..46d02551c 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -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" diff --git a/app/templates/about.jade b/app/templates/about.jade index b7f3d81ac..a0be8a7fe 100644 --- a/app/templates/about.jade +++ b/app/templates/about.jade @@ -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 From ac4df997c14a0afd4c134a90d82a89a788f026bf Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 26 Jul 2016 16:47:55 -0700 Subject: [PATCH 2/6] Display treema errors --- app/views/core/RootView.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/views/core/RootView.coffee b/app/views/core/RootView.coffee index 75ed205a3..8e29b9e12 100644 --- a/app/views/core/RootView.coffee +++ b/app/views/core/RootView.coffee @@ -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 From fd1779381944552f23ebf7ba27db970fdf19d2e8 Mon Sep 17 00:00:00 2001 From: Rob Date: Wed, 27 Jul 2016 13:43:22 -0700 Subject: [PATCH 3/6] Add user completion time analysis script for students. --- .../analytics/analyzeClassCompletionTimes.js | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 scripts/analytics/analyzeClassCompletionTimes.js diff --git a/scripts/analytics/analyzeClassCompletionTimes.js b/scripts/analytics/analyzeClassCompletionTimes.js new file mode 100644 index 000000000..151d24923 --- /dev/null +++ b/scripts/analytics/analyzeClassCompletionTimes.js @@ -0,0 +1,105 @@ +if ( typeof password === 'undefined' ) { + throw "Please specify the coco user password on the commandline as --eval '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, ' ')); + From 75e3c54d5430369b34307e7bbffa8d7892ef5ad6 Mon Sep 17 00:00:00 2001 From: Rob Date: Tue, 19 Jul 2016 17:58:10 -0700 Subject: [PATCH 4/6] Add concept map view to artisans. --- app/core/Router.coffee | 1 + app/templates/artisans/artisans-view.jade | 5 +- app/templates/artisans/concept-map-view.jade | 20 +++ app/views/artisans/LevelConceptMap.coffee | 168 +++++++++++++++++++ 4 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 app/templates/artisans/concept-map-view.jade create mode 100644 app/views/artisans/LevelConceptMap.coffee diff --git a/app/core/Router.coffee b/app/core/Router.coffee index b80e7f671..3b9359c98 100644 --- a/app/core/Router.coffee +++ b/app/core/Router.coffee @@ -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') diff --git a/app/templates/artisans/artisans-view.jade b/app/templates/artisans/artisans-view.jade index c2b5b3215..2f6321319 100644 --- a/app/templates/artisans/artisans-view.jade +++ b/app/templates/artisans/artisans-view.jade @@ -10,4 +10,7 @@ block content |Level Tasks div a(href="/artisans/solution-problems") - |Solution Problems \ No newline at end of file + |Solution Problems + div + a(href="/artisans/level-concepts") + |Level Concept Map \ No newline at end of file diff --git a/app/templates/artisans/concept-map-view.jade b/app/templates/artisans/concept-map-view.jade new file mode 100644 index 000000000..67156a7d8 --- /dev/null +++ b/app/templates/artisans/concept-map-view.jade @@ -0,0 +1,20 @@ +// DNT +extends /templates/base + +block content + #solution-problems-view + div + a(href='/artisans') + span.glyphicon.glyphicon-chevron-left + span Artisans Home + br + table.table.table-striped#level-table + tr + th Level Name + th Concepts Detected + for level in (view.parsedLevels || []) + tr + td(style="width:10%")= level.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 \ No newline at end of file diff --git a/app/views/artisans/LevelConceptMap.coffee b/app/views/artisans/LevelConceptMap.coffee new file mode 100644 index 000000000..97149d251 --- /dev/null +++ b/app/views/artisans/LevelConceptMap.coffee @@ -0,0 +1,168 @@ +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: {} + parsedLevels: [] + 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) + @supermodel.trackRequest(@campaignLevels[campaignSlug].fetchForCampaign(campaignSlug, + data: + project: 'thangs,name,slug,campaign' + )) + + onLevelsLoaded: (lvlCollection) -> + for level in lvlCollection.models + @loadedLevels[level.get('slug')] = level + if --@unloadedCampaigns is 0 + @onAllLevelsLoaded() + + onAllLevelsLoaded: -> + for levelSlug, level of @loadedLevels + 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 + @parsedLevels.push + level: level + tags: @tagLevel _.find plan.solutions, (s) -> s.language is 'javascript' + + @renderSelectors '#level-table' + + 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) + From 6c9f351f010ceded7ff98044199f8ef4dcb9a122 Mon Sep 17 00:00:00 2001 From: Rob Date: Wed, 27 Jul 2016 14:17:37 -0700 Subject: [PATCH 5/6] Group concept map by campaign. --- app/templates/artisans/concept-map-view.jade | 22 +++--- app/views/artisans/LevelConceptMap.coffee | 71 +++++++++++--------- 2 files changed, 51 insertions(+), 42 deletions(-) diff --git a/app/templates/artisans/concept-map-view.jade b/app/templates/artisans/concept-map-view.jade index 67156a7d8..50368de79 100644 --- a/app/templates/artisans/concept-map-view.jade +++ b/app/templates/artisans/concept-map-view.jade @@ -8,13 +8,17 @@ block content span.glyphicon.glyphicon-chevron-left span Artisans Home br - table.table.table-striped#level-table - tr - th Level Name - th Concepts Detected - for level in (view.parsedLevels || []) + for course, k in (view.data || {}) + h3= k + table.table.table-striped tr - td(style="width:10%")= level.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 \ No newline at end of file + 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 \ No newline at end of file diff --git a/app/views/artisans/LevelConceptMap.coffee b/app/views/artisans/LevelConceptMap.coffee index 97149d251..13c1183c2 100644 --- a/app/views/artisans/LevelConceptMap.coffee +++ b/app/views/artisans/LevelConceptMap.coffee @@ -37,7 +37,7 @@ module.exports = class LevelConceptMap extends RootView unloadedCampaigns: 0 campaignLevels: {} loadedLevels: {} - parsedLevels: [] + data: {} problemCount: 0 initialize: -> @@ -55,53 +55,58 @@ module.exports = class LevelConceptMap extends RootView @unloadedCampaigns++ @campaignLevels[campaignSlug] = new Levels() - @listenTo(@campaignLevels[campaignSlug], 'sync', @onLevelsLoaded) + @listenTo(@campaignLevels[campaignSlug], 'sync', @onLevelsLoaded.bind @, campaignSlug) @supermodel.trackRequest(@campaignLevels[campaignSlug].fetchForCampaign(campaignSlug, data: project: 'thangs,name,slug,campaign' )) - onLevelsLoaded: (lvlCollection) -> - for level in lvlCollection.models - @loadedLevels[level.get('slug')] = level + 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 levelSlug, level of @loadedLevels - unless level? - console.error 'Level Slug doesn\'t have associated Level', levelSlug - continue + 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 + 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 + 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 + unless component? + console.error 'Level doesn\'t have programmableMethod Thang', levelSlug + continue - plan = component.config.programmableMethods.plan - @parsedLevels.push - level: level - tags: @tagLevel _.find plan.solutions, (s) -> s.language is 'javascript' + plan = component.config.programmableMethods.plan + level.tags = @tagLevel _.find plan.solutions, (s) -> s.language is 'javascript' + @data[campaignSlug] = _.sortBy _.values(@loadedLevels[campaignSlug]), 'seqNo' - @renderSelectors '#level-table' + console.log @render, @loadedLevels + @render() tagLevel: (src) -> return [] if not src?.source? From e3453cb0f348ca783dbb00ba575c9beb3d6600c6 Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Wed, 27 Jul 2016 15:57:35 -0700 Subject: [PATCH 6/6] Update Simulator to new, simple way of making UserCodeMap --- app/lib/simulator/Simulator.coffee | 109 ++++------------------------- 1 file changed, 14 insertions(+), 95 deletions(-) diff --git a/app/lib/simulator/Simulator.coffee b/app/lib/simulator/Simulator.coffee index 16e48dd6a..7c26e6c08 100644 --- a/app/lib/simulator/Simulator.coffee +++ b/app/lib/simulator/Simulator.coffee @@ -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 + 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 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 - 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 @@ -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