diff --git a/app/assets/javascripts/workers/worker_world.js b/app/assets/javascripts/workers/worker_world.js
index dcfc5b13b..154678395 100644
--- a/app/assets/javascripts/workers/worker_world.js
+++ b/app/assets/javascripts/workers/worker_world.js
@@ -428,17 +428,18 @@ self.onWorldLoaded = function onWorldLoaded() {
if(self.world.framesSerializedSoFar == self.world.frames.length) return;
if(self.world.ended)
self.goalManager.worldGenerationEnded();
- var goalStates = self.goalManager.getGoalStates();
- var overallStatus = self.goalManager.checkOverallStatus();
- var totalFrames = self.world.totalFrames;
- if(self.world.ended) {
- var lastFrameHash = self.world.frames[totalFrames - 2].hash
- self.postMessage({type: 'end-load-frames', goalStates: goalStates, overallStatus: overallStatus, totalFrames: totalFrames, lastFrameHash: lastFrameHash});
- }
var t1 = new Date();
var diff = t1 - self.t0;
- if(self.world.headless)
- return console.log('Headless simulation completed in ' + diff + 'ms.');
+ var goalStates = self.goalManager.getGoalStates();
+ var totalFrames = self.world.totalFrames;
+ if(self.world.ended) {
+ var overallStatus = self.goalManager.checkOverallStatus();
+ var lastFrameHash = self.world.frames[totalFrames - 2].hash
+ var simulationFrameRate = self.world.frames.length / diff * 1000 * 30 / self.world.frameRate
+ self.postMessage({type: 'end-load-frames', goalStates: goalStates, overallStatus: overallStatus, totalFrames: totalFrames, lastFrameHash: lastFrameHash, simulationFrameRate: simulationFrameRate});
+ if(self.world.headless)
+ return console.log('Headless simulation completed in ' + diff + 'ms, ' + simulationFrameRate.toFixed(1) + ' FPS.');
+ }
var worldEnded = self.world.ended;
var serialized;
@@ -469,7 +470,7 @@ self.onWorldLoaded = function onWorldLoaded() {
if(worldEnded) {
var t3 = new Date();
- console.log("And it was so: (" + (diff / totalFrames).toFixed(3) + "ms per frame,", totalFrames, "frames)\nSimulation :", diff + "ms \nSerialization:", (t2 - t1) + "ms\nDelivery :", (t3 - t2) + "ms");
+ console.log("And it was so: (" + (diff / totalFrames).toFixed(3) + "ms per frame,", totalFrames, "frames)\nSimulation :", diff + "ms \nSerialization:", (t2 - t1) + "ms\nDelivery :", (t3 - t2) + "ms\nFPS :", simulationFrameRate.toFixed(1));
}
};
@@ -483,7 +484,10 @@ self.onWorldPreloaded = function onWorldPreloaded() {
self.goalManager.worldGenerationEnded();
var goalStates = self.goalManager.getGoalStates();
var overallStatus = self.goalManager.checkOverallStatus();
- self.postMessage({type: 'end-preload-frames', goalStates: goalStates, overallStatus: overallStatus});
+ var t1 = new Date();
+ var diff = t1 - self.t0;
+ var simulationFrameRate = self.world.frames.length / diff * 1000 * 30 / self.world.frameRate
+ self.postMessage({type: 'end-preload-frames', goalStates: goalStates, overallStatus: overallStatus, simulationFrameRate: simulationFrameRate});
};
self.onWorldError = function onWorldError(error) {
diff --git a/app/core/utils.coffee b/app/core/utils.coffee
index 028105538..e8c29bd88 100644
--- a/app/core/utils.coffee
+++ b/app/core/utils.coffee
@@ -370,3 +370,103 @@ module.exports.findNextLevel = (levels, currentIndex, needsPractice) ->
module.exports.needsPractice = (playtime=0, threshold=2) ->
playtime / 60 > threshold
+
+module.exports.usStateCodes =
+ # https://github.com/mdzhang/us-state-codes
+ # generated by js2coffee 2.2.0
+ (->
+ stateNamesByCode =
+ 'AL': 'Alabama'
+ 'AK': 'Alaska'
+ 'AZ': 'Arizona'
+ 'AR': 'Arkansas'
+ 'CA': 'California'
+ 'CO': 'Colorado'
+ 'CT': 'Connecticut'
+ 'DE': 'Delaware'
+ 'DC': 'District of Columbia'
+ 'FL': 'Florida'
+ 'GA': 'Georgia'
+ 'HI': 'Hawaii'
+ 'ID': 'Idaho'
+ 'IL': 'Illinois'
+ 'IN': 'Indiana'
+ 'IA': 'Iowa'
+ 'KS': 'Kansas'
+ 'KY': 'Kentucky'
+ 'LA': 'Louisiana'
+ 'ME': 'Maine'
+ 'MD': 'Maryland'
+ 'MA': 'Massachusetts'
+ 'MI': 'Michigan'
+ 'MN': 'Minnesota'
+ 'MS': 'Mississippi'
+ 'MO': 'Missouri'
+ 'MT': 'Montana'
+ 'NE': 'Nebraska'
+ 'NV': 'Nevada'
+ 'NH': 'New Hampshire'
+ 'NJ': 'New Jersey'
+ 'NM': 'New Mexico'
+ 'NY': 'New York'
+ 'NC': 'North Carolina'
+ 'ND': 'North Dakota'
+ 'OH': 'Ohio'
+ 'OK': 'Oklahoma'
+ 'OR': 'Oregon'
+ 'PA': 'Pennsylvania'
+ 'RI': 'Rhode Island'
+ 'SC': 'South Carolina'
+ 'SD': 'South Dakota'
+ 'TN': 'Tennessee'
+ 'TX': 'Texas'
+ 'UT': 'Utah'
+ 'VT': 'Vermont'
+ 'VA': 'Virginia'
+ 'WA': 'Washington'
+ 'WV': 'West Virginia'
+ 'WI': 'Wisconsin'
+ 'WY': 'Wyoming'
+ stateCodesByName = _.invert(stateNamesByCode)
+ # normalizes case and removes invalid characters
+ # returns null if can't find sanitized code in the state map
+
+ sanitizeStateCode = (code) ->
+ code = if _.isString(code) then code.trim().toUpperCase().replace(/[^A-Z]/g, '') else null
+ if stateNamesByCode[code] then code else null
+
+ # returns a valid state name else null
+
+ getStateNameByStateCode = (code) ->
+ stateNamesByCode[sanitizeStateCode(code)] or null
+
+ # normalizes case and removes invalid characters
+ # returns null if can't find sanitized name in the state map
+
+ sanitizeStateName = (name) ->
+ if !_.isString(name)
+ return null
+ # bad whitespace remains bad whitespace e.g. "O hi o" is not valid
+ name = name.trim().toLowerCase().replace(/[^a-z\s]/g, '').replace(/\s+/g, ' ')
+ tokens = name.split(/\s+/)
+ tokens = _.map(tokens, (token) ->
+ token.charAt(0).toUpperCase() + token.slice(1)
+ )
+ # account for District of Columbia
+ if tokens.length > 2
+ tokens[1] = tokens[1].toLowerCase()
+ name = tokens.join(' ')
+ if stateCodesByName[name] then name else null
+
+ # returns a valid state code else null
+
+ getStateCodeByStateName = (name) ->
+ stateCodesByName[sanitizeStateName(name)] or null
+
+ return {
+ sanitizeStateCode: sanitizeStateCode
+ getStateNameByStateCode: getStateNameByStateCode
+ sanitizeStateName: sanitizeStateName
+ getStateCodeByStateName: getStateCodeByStateName
+ }
+ )()
diff --git a/app/lib/Angel.coffee b/app/lib/Angel.coffee
index 4b410d0a1..923177eeb 100644
--- a/app/lib/Angel.coffee
+++ b/app/lib/Angel.coffee
@@ -82,11 +82,10 @@ module.exports = class Angel extends CocoClass
clearTimeout @condemnTimeout
when 'end-load-frames'
clearTimeout @condemnTimeout
- @beholdGoalStates event.data.goalStates, event.data.overallStatus, false, event.data.totalFrames, event.data.lastFrameHash # Work ends here if we're headless.
+ @beholdGoalStates {goalStates: event.data.goalStates, overallStatus: event.data.overallStatus, preload: false, totalFrames: event.data.totalFrames, lastFrameHash: event.data.lastFrameHash, simulationFrameRate: event.data.simulationFrameRate} # Work ends here if we're headless.
when 'end-preload-frames'
clearTimeout @condemnTimeout
- @beholdGoalStates event.data.goalStates, event.data.overallStatus, true
-
+ @beholdGoalStates {goalStates: event.data.goalStates, overallStatus: event.data.overallStatus, preload: true, simulationFrameRate: event.data.simulationFrameRate}
# We have to abort like an infinite loop if we see one of these; they're not really recoverable
when 'non-user-code-problem'
@@ -125,11 +124,12 @@ module.exports = class Angel extends CocoClass
else
@log 'Received unsupported message:', event.data
- beholdGoalStates: (goalStates, overallStatus, preload=false, totalFrames=undefined, lastFrameHash=undefined) ->
+ beholdGoalStates: ({goalStates, overallStatus, preload, totalFrames, lastFrameHash, simulationFrameRate}) ->
return if @aborting
- event = goalStates: goalStates, preload: preload, overallStatus: overallStatus
+ event = goalStates: goalStates, preload: preload ? false, overallStatus: overallStatus
event.totalFrames = totalFrames if totalFrames?
event.lastFrameHash = lastFrameHash if lastFrameHash?
+ event.simulationFrameRate = simulationFrameRate if simulationFrameRate?
@publishGodEvent 'goals-calculated', event
@finishWork() if @shared.headless
@@ -306,7 +306,8 @@ module.exports = class Angel extends CocoClass
work.world.goalManager.worldGenerationEnded() if work.world.ended
if work.headless
- @beholdGoalStates goalStates, testGM.checkOverallStatus(), false, work.world.totalFrames, work.world.frames[work.world.totalFrames - 2]?.hash
+ simulationFrameRate = work.world.frames.length / (work.t2 - work.t1) * 1000 * 30 / work.world.frameRate
+ @beholdGoalStates {goalStates, overallStatus: testGM.checkOverallStatus(), preload: false, totalFrames: work.world.totalFrames, lastFrameHash: work.world.frames[work.world.totalFrames - 2]?.hash, simulationFrameRate: simulationFrameRate}
return
serialized = world.serialize()
diff --git a/app/lib/surface/Camera.coffee b/app/lib/surface/Camera.coffee
index 0e37a5a12..c6b6ad65c 100644
--- a/app/lib/surface/Camera.coffee
+++ b/app/lib/surface/Camera.coffee
@@ -1,4 +1,5 @@
CocoClass = require 'core/CocoClass'
+GameUIState = require 'models/GameUIState'
# If I were the kind of math major who remembered his math, this would all be done with matrix transforms.
@@ -44,12 +45,17 @@ module.exports = class Camera extends CocoClass
'camera:zoom-out': 'onZoomOut'
'camera:zoom-to': 'onZoomTo'
'level:restarted': 'onLevelRestarted'
- 'surface:mouse-scrolled': 'onMouseScrolled'
- 'sprite:mouse-down': 'onMouseDown'
- 'sprite:dragged': 'onMouseDragged'
- constructor: (@canvas, angle=Math.asin(0.75), hFOV=d2r(30)) ->
+ constructor: (@canvas, @options={}) ->
+ angle=Math.asin(0.75)
+ hFOV=d2r(30)
super()
+ @gameUIState = @options.gameUIState or new GameUIState()
+ @listenTo @gameUIState, 'surface:stage-mouse-move', @onMouseMove
+ @listenTo @gameUIState, 'surface:stage-mouse-down', @onMouseDown
+ @listenTo @gameUIState, 'surface:stage-mouse-up', @onMouseUp
+ @listenTo @gameUIState, 'surface:mouse-scrolled', @onMouseScrolled
+ @handleEvents = @options.handleEvents ? true
@canvasWidth = parseInt(@canvas.attr('width'), 10)
@canvasHeight = parseInt(@canvas.attr('height'), 10)
@offset = {x: 0, y: 0}
@@ -155,8 +161,27 @@ module.exports = class Camera extends CocoClass
onZoomIn: (e) -> @zoomTo @target, @zoom * 1.15, 300
onZoomOut: (e) -> @zoomTo @target, @zoom / 1.15, 300
+
+ onMouseDown: (e) ->
+ return if @dragDisabled
+ @lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
+ @mousePressed = true
+
+ onMouseMove: (e) ->
+ return unless @mousePressed and @gameUIState.get('canDragCamera')
+ return if @dragDisabled
+ target = @boundTarget(@target, @zoom)
+ newPos =
+ x: target.x + (@lastPos.x - e.originalEvent.rawX) / @zoom
+ y: target.y + (@lastPos.y - e.originalEvent.rawY) / @zoom
+ @zoomTo newPos, @zoom, 0
+ @lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
+ Backbone.Mediator.publish 'camera:dragged', {}
+
+ onMouseUp: (e) ->
+ @mousePressed = false
+
onMouseScrolled: (e) ->
- return unless e.canvas is @canvas
ratio = 1 + 0.05 * Math.sqrt(Math.abs(e.deltaY))
ratio = 1 / ratio if e.deltaY > 0
newZoom = @zoom * ratio
@@ -174,22 +199,6 @@ module.exports = class Camera extends CocoClass
target = @target
@zoomTo target, newZoom, 0
- onMouseDown: (e) ->
- return unless e.canvas is @canvas[0]
- return if @dragDisabled
- @lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
-
- onMouseDragged: (e) ->
- return unless e.canvas is @canvas[0]
- return if @dragDisabled
- target = @boundTarget(@target, @zoom)
- newPos =
- x: target.x + (@lastPos.x - e.originalEvent.rawX) / @zoom
- y: target.y + (@lastPos.y - e.originalEvent.rawY) / @zoom
- @zoomTo newPos, @zoom, 0
- @lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
- Backbone.Mediator.publish 'camera:dragged', {}
-
onLevelRestarted: ->
@setBounds(@firstBounds, false)
diff --git a/app/lib/surface/Lank.coffee b/app/lib/surface/Lank.coffee
index a116f3fa2..42dd9a150 100644
--- a/app/lib/surface/Lank.coffee
+++ b/app/lib/surface/Lank.coffee
@@ -57,12 +57,14 @@ module.exports = Lank = class Lank extends CocoClass
'level:set-letterbox': 'onSetLetterbox'
'surface:ticked': 'onSurfaceTicked'
'sprite:move': 'onMove'
-
- constructor: (@thangType, options) ->
+
+ constructor: (@thangType, options={}) ->
super()
spriteName = @thangType.get('name')
@isMissile = /(Missile|Arrow|Spear|Bolt)/.test(spriteName) and not /(Tower|Charge)/.test(spriteName)
@options = _.extend($.extend(true, {}, @options), options)
+ @gameUIState = @options.gameUIState
+ @handleEvents = @options.handleEvents
@setThang @options.thang
if @thang?
options = @thang?.getLankOptions?()
@@ -496,6 +498,7 @@ module.exports = Lank = class Lank extends CocoClass
newEvent = sprite: @, thang: @thang, originalEvent: e, canvas: p.canvas
@trigger ourEventName, newEvent
Backbone.Mediator.publish ourEventName, newEvent
+ @gameUIState.trigger(ourEventName, newEvent)
addHealthBar: ->
return unless @thang?.health? and 'health' in (@thang?.hudProperties ? []) and @options.floatingLayer
@@ -645,6 +648,10 @@ module.exports = Lank = class Lank extends CocoClass
addMark: (name, layer, thangType=null) ->
@marks[name] ?= new Mark name: name, lank: @, camera: @options.camera, layer: layer ? @options.groundLayer, thangType: thangType
@marks[name]
+
+ removeMark: (name) ->
+ @marks[name].destroy()
+ delete @marks[name]
notifySpeechUpdated: (e) ->
e = _.clone(e)
diff --git a/app/lib/surface/LankBoss.coffee b/app/lib/surface/LankBoss.coffee
index d1187c648..51296843f 100644
--- a/app/lib/surface/LankBoss.coffee
+++ b/app/lib/surface/LankBoss.coffee
@@ -25,10 +25,11 @@ module.exports = class LankBoss extends CocoClass
'surface:flag-appeared': 'onFlagAppeared'
'surface:remove-selected-flag': 'onRemoveSelectedFlag'
- constructor: (@options) ->
+ constructor: (@options={}) ->
super()
+ @handleEvents = @options.handleEvents
+ @gameUIState = @options.gameUIState
@dragged = 0
- @options ?= {}
@camera = @options.camera
@webGLStage = @options.webGLStage
@surfaceTextLayer = @options.surfaceTextLayer
@@ -38,6 +39,8 @@ module.exports = class LankBoss extends CocoClass
@lankArray = [] # Mirror @lanks, but faster for when we just need to iterate
@createLayers()
@pendingFlags = []
+ if not @handleEvents
+ @listenTo @gameUIState, 'change:selected', @onChangeSelected
destroy: ->
@removeLank lank for thangID, lank of @lanks
@@ -93,7 +96,16 @@ module.exports = class LankBoss extends CocoClass
@selectionMark = new Mark name: 'selection', camera: @camera, layer: @layerAdapters['Ground'], thangType: 'selection'
createLankOptions: (options) ->
- _.extend options, camera: @camera, resolutionFactor: SPRITE_RESOLUTION_FACTOR, groundLayer: @layerAdapters['Ground'], textLayer: @surfaceTextLayer, floatingLayer: @layerAdapters['Floating'], showInvisible: @options.showInvisible
+ _.extend options, {
+ @camera
+ resolutionFactor: SPRITE_RESOLUTION_FACTOR
+ groundLayer: @layerAdapters['Ground']
+ textLayer: @surfaceTextLayer
+ floatingLayer: @layerAdapters['Floating']
+ showInvisible: @options.showInvisible
+ @gameUIState
+ @handleEvents
+ }
onSetDebug: (e) ->
return if e.debug is @debug
@@ -256,6 +268,7 @@ module.exports = class LankBoss extends CocoClass
@dragged += 1
onLankMouseUp: (e) ->
+ return unless @handleEvents
return if key.shift #and @options.choosing
return @dragged = 0 if @dragged > 3
@dragged = 0
@@ -264,9 +277,27 @@ module.exports = class LankBoss extends CocoClass
@selectLank e, lank
onStageMouseDown: (e) ->
+ return unless @handleEvents
return if key.shift #and @options.choosing
@selectLank e if e.onBackground
+ onChangeSelected: (gameUIState, selected) ->
+ oldLanks = (s.sprite for s in gameUIState.previousAttributes().selected or [])
+ newLanks = (s.sprite for s in selected or [])
+ addedLanks = _.difference(newLanks, oldLanks)
+ removedLanks = _.difference(oldLanks, newLanks)
+
+ for lank in addedLanks
+ layer = if lank.sprite.parent isnt @layerAdapters.Default.container then @layerAdapters.Default else @layerAdapters.Ground
+ mark = new Mark name: 'selection', camera: @camera, layer: layer, thangType: 'selection'
+ mark.toggle true
+ mark.setLank(lank)
+ mark.update()
+ lank.marks.selection = mark # TODO: Figure out how to non-hackily assign lank this mark
+
+ for lank in removedLanks
+ lank.removeMark?('selection')
+
selectThang: (thangID, spellName=null, treemaThangSelected = null) ->
return @willSelectThang = [thangID, spellName] unless @lanks[thangID]
@selectLank null, @lanks[thangID], spellName, treemaThangSelected
@@ -275,8 +306,9 @@ module.exports = class LankBoss extends CocoClass
return if e and (@disabled or @selectLocked) # Ignore clicks for selection/panning/wizard movement while disabled or select is locked
worldPos = lank?.thang?.pos
worldPos ?= @camera.screenToWorld {x: e.originalEvent.rawX, y: e.originalEvent.rawY} if e?.originalEvent
- if (not @reallyStopMoving) and worldPos and (@options.navigateToSelection or not lank or treemaThangSelected) and e?.originalEvent?.nativeEvent?.which isnt 3
- @camera.zoomTo(lank?.sprite or @camera.worldToSurface(worldPos), @camera.zoom, 1000, true)
+ if @handleEvents
+ if (not @reallyStopMoving) and worldPos and (@options.navigateToSelection or not lank or treemaThangSelected) and e?.originalEvent?.nativeEvent?.which isnt 3
+ @camera.zoomTo(lank?.sprite or @camera.worldToSurface(worldPos), @camera.zoom, 1000, true)
lank = null if @options.choosing # Don't select lanks while choosing
if lank isnt @selectedLank
@selectedLank?.selected = false
diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee
index 51b2e2789..7e6cf6482 100644
--- a/app/lib/surface/Surface.coffee
+++ b/app/lib/surface/Surface.coffee
@@ -18,6 +18,7 @@ LankBoss = require './LankBoss'
PointChooser = require './PointChooser'
RegionChooser = require './RegionChooser'
MusicPlayer = require './MusicPlayer'
+GameUIState = require 'models/GameUIState'
resizeDelay = 500 # At least as much as $level-resize-transition-time.
@@ -87,6 +88,10 @@ module.exports = Surface = class Surface extends CocoClass
@normalLayers = []
@options = _.clone(@defaults)
@options = _.extend(@options, givenOptions) if givenOptions
+ @handleEvents = @options.handleEvents ? true
+ @gameUIState = @options.gameUIState or new GameUIState({
+ canDragCamera: true
+ })
@initEasel()
@initAudio()
@onResize = _.debounce @onResize, resizeDelay
@@ -98,7 +103,7 @@ module.exports = Surface = class Surface extends CocoClass
@normalStage = new createjs.Stage(@normalCanvas[0])
@webGLStage = new createjs.SpriteStage(@webGLCanvas[0])
@normalStage.nextStage = @webGLStage
- @camera = new Camera @webGLCanvas
+ @camera = new Camera(@webGLCanvas, { @gameUIState, @handleEvents })
AudioPlayer.camera = @camera unless @options.choosing
@normalLayers.push @surfaceTextLayer = new Layer name: 'Surface Text', layerPriority: 1, transform: Layer.TRANSFORM_SURFACE_TEXT, camera: @camera
@@ -112,7 +117,19 @@ module.exports = Surface = class Surface extends CocoClass
canvasHeight = parseInt @normalCanvas.attr('height'), 10
@screenLayer.addChild new Letterbox canvasWidth: canvasWidth, canvasHeight: canvasHeight
- @lankBoss = new LankBoss camera: @camera, webGLStage: @webGLStage, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible, playerNames: if @options.levelType is 'course-ladder' then @options.playerNames else null
+ @lankBoss = new LankBoss({
+ @camera
+ @webGLStage
+ @surfaceTextLayer
+ @world
+ thangTypes: @options.thangTypes
+ choosing: @options.choosing
+ navigateToSelection: @options.navigateToSelection
+ showInvisible: @options.showInvisible
+ playerNames: if @options.levelType is 'course-ladder' then @options.playerNames else null
+ @gameUIState
+ @handleEvents
+ })
@countdownScreen = new CountdownScreen camera: @camera, layer: @screenLayer, showsCountdown: @world.showsCountdown
@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.
@@ -121,7 +138,7 @@ module.exports = Surface = class Surface extends CocoClass
@webGLStage.enableMouseOver(10)
@webGLStage.addEventListener 'stagemousemove', @onMouseMove
@webGLStage.addEventListener 'stagemousedown', @onMouseDown
- @webGLCanvas[0].addEventListener 'mouseup', @onMouseUp
+ @webGLStage.addEventListener 'stagemouseup', @onMouseUp
@webGLCanvas.on 'mousewheel', @onMouseWheel
@hookUpChooseControls() if @options.choosing # TODO: figure this stuff out
createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED
@@ -222,8 +239,9 @@ module.exports = Surface = class Surface extends CocoClass
updateState: (frameChanged) ->
# world state must have been restored in @restoreWorldState
- if @playing and @currentFrame < @world.frames.length - 1 and @heroLank and not @mouseIsDown and @camera.newTarget isnt @heroLank.sprite and @camera.target isnt @heroLank.sprite
- @camera.zoomTo @heroLank.sprite, @camera.zoom, 750
+ if @handleEvents
+ if @playing and @currentFrame < @world.frames.length - 1 and @heroLank and not @mouseIsDown and @camera.newTarget isnt @heroLank.sprite and @camera.target isnt @heroLank.sprite
+ @camera.zoomTo @heroLank.sprite, @camera.zoom, 750
@lankBoss.update frameChanged
@camera.updateZoom() # Make sure to do this right after the LankBoss updates, not before, so it can properly target sprite positions.
@dimmer?.setSprites @lankBoss.lanks
@@ -371,7 +389,8 @@ module.exports = Surface = class Surface extends CocoClass
target = null
@camera.setBounds e.bounds if e.bounds
# @cameraBorder.updateBounds @camera.bounds
- @camera.zoomTo target, e.zoom, e.duration # TODO: SurfaceScriptModule perhaps shouldn't assign e.zoom if not set
+ if @handleEvents
+ @camera.zoomTo target, e.zoom, e.duration # TODO: SurfaceScriptModule perhaps shouldn't assign e.zoom if not set
onZoomUpdated: (e) ->
if @ended
@@ -476,6 +495,7 @@ module.exports = Surface = class Surface extends CocoClass
@mouseScreenPos = {x: e.stageX, y: e.stageY}
return if @disabled
Backbone.Mediator.publish 'surface:mouse-moved', x: e.stageX, y: e.stageY
+ @gameUIState.trigger('surface:stage-mouse-move', { originalEvent: e })
onMouseDown: (e) =>
return if @disabled
@@ -484,16 +504,19 @@ module.exports = Surface = class Surface extends CocoClass
onBackground = not @webGLStage._getObjectsUnderPoint(e.stageX, e.stageY, null, true)
wop = @camera.screenToWorld x: e.stageX, y: e.stageY
- event = onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e, worldPos: wop
+ event = { onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e, worldPos: wop }
Backbone.Mediator.publish 'surface:stage-mouse-down', event
Backbone.Mediator.publish 'tome:focus-editor', {}
+ @gameUIState.trigger('surface:stage-mouse-down', event)
@mouseIsDown = true
onMouseUp: (e) =>
return if @disabled
onBackground = not @webGLStage.hitTest e.stageX, e.stageY
- Backbone.Mediator.publish 'surface:stage-mouse-up', onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e
+ event = { onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e }
+ Backbone.Mediator.publish 'surface:stage-mouse-up', event
Backbone.Mediator.publish 'tome:focus-editor', {}
+ @gameUIState.trigger('surface:stage-mouse-up', event)
@mouseIsDown = false
onMouseWheel: (e) =>
@@ -506,6 +529,7 @@ module.exports = Surface = class Surface extends CocoClass
canvas: @webGLCanvas
event.screenPos = @mouseScreenPos if @mouseScreenPos
Backbone.Mediator.publish 'surface:mouse-scrolled', event unless @disabled
+ @gameUIState.trigger('surface:mouse-scrolled', event)
#- Canvas callbacks
@@ -585,8 +609,9 @@ module.exports = Surface = class Surface extends CocoClass
@onResize()
_.delay @onResize, resizeDelay + 100 # Do it again just to be double sure that we don't stay zoomed in due to timing problems.
@normalCanvas.add(@webGLCanvas).removeClass 'flag-color-selected'
- if @previousCameraZoom
- @camera.zoomTo @camera.newTarget or @camera.target, @previousCameraZoom, 3000
+ if @handleEvents
+ if @previousCameraZoom
+ @camera.zoomTo @camera.newTarget or @camera.target, @previousCameraZoom, 3000
onFlagColorSelected: (e) ->
@normalCanvas.add(@webGLCanvas).toggleClass 'flag-color-selected', Boolean(e.color)
diff --git a/app/locale/nb.coffee b/app/locale/nb.coffee
index 5fa342385..fdf67e34f 100644
--- a/app/locale/nb.coffee
+++ b/app/locale/nb.coffee
@@ -14,16 +14,16 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
for_developers: "For Utviklere" # Not currently shown on home page.
or_ipad: "Eller last ned til iPad"
-# new_home:
+ new_home:
# slogan: "The most engaging game for learning programming."
# classroom_edition: "Classroom Edition:"
-# learn_to_code: "Learn to code:"
-# teacher: "Teacher"
-# student: "Student"
-# play_now: "Play Now"
-# im_a_teacher: "I'm a Teacher"
-# im_a_student: "I'm a Student"
-# learn_more: "Learn more"
+ learn_to_code: "Lær å kode:"
+ teacher: "Lærer"
+ student: "Student"
+ play_now: "Spill nå"
+ im_a_teacher: "Jeg er en lærer"
+ im_a_student: "Jeg er en student"
+ learn_more: "Lær mer"
# classroom_in_a_box: "A classroom in-a-box for teaching computer science."
# codecombat_is: "CodeCombat is a platform for students to learn computer science while playing through a real game."
# our_courses: "Our courses have been specifically playtested to excel in the classroom, even by teachers with little to no prior programming experience."
@@ -46,7 +46,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# excel: "Games excel at rewarding"
# struggle: "productive struggle"
# kind_of_struggle: "the kind of struggle that results in learning that’s engaging and"
-# motivating: "motivating"
+ motivating: "motiverende"
# not_tedious: "not tedious."
# gaming_is_good: "Studies suggest gaming is good for children’s brains. (it’s true!)"
# game_based: "When game-based learning systems are"
@@ -69,40 +69,40 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# computer_science: "Computer science courses for all ages"
# show_me_lesson_time: "Show me lesson time estimates for:"
# curriculum: "Total curriculum hours:"
-# ffa: "Free for all students"
+ ffa: "Gratis for alle studenter"
# lesson_time: "Lesson time:"
-# coming_soon: "Coming soon!"
+ coming_soon: "Kommer snart!"
# courses_available_in: "Courses are available in JavaScript, Python, and Java (coming soon!)"
# boast: "Boasts riddles that are complex enough to fascinate gamers and coders alike."
# winning: "A winning combination of RPG gameplay and programming homework that pulls off making kid-friendly education legitimately enjoyable."
# run_class: "Everything you need to run a computer science class in your school today, no CS background required."
-# teachers: "Teachers!"
+ teachers: "Teachers!"
# teachers_and_educators: "Teachers & Educators"
# class_in_box: "Learn how our classroom-in-a-box platform fits into your curriculum."
# get_started: "Get Started"
-# students: "Students:"
+ students: "Studenter:"
# join_class: "Join Class"
-# role: "Your role:"
-# student_count: "Number of students:"
+ role: "Din rolle:"
+ student_count: "Antall studenter:"
# start_playing_for_free: "Start Playing for Free!"
-# students_and_players: "Students & Players"
-# goto_classes: "Go to My Classes"
-# view_profile: "View My Profile"
-# view_progress: "View Progress"
+ students_and_players: "Studenter & Spillere"
+ goto_classes: "Gå til klassene mine"
+ view_profile: "Vis profilen min"
+ view_progress: "Vis Framgang"
# check_out_wiki: "Check out our new Educator Wiki"
# want_coco: "Want CodeCombat at your school?"
-# form_select_role: "Select primary role"
+ form_select_role: "Velg Hovedrolle"
# form_select_range: "Select class size"
nav:
play: "Spill" # The top nav bar entry where players choose which levels to play
community: "Fellesskap"
-# courses: "Courses"
+ courses: "Emner"
editor: "Editor"
blog: "Blogg"
forum: "Forum"
account: "Konto"
-# my_account: "My Account"
+ my_account: "Min bruker"
profile: "Profil"
stats: "Statistikk"
code: "Kode"
@@ -112,19 +112,19 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
about: "Om"
contact: "Kontakt"
twitter_follow: "Følg"
-# students: "Students"
+ students: "Studenter"
teachers: "Lærere"
-# careers: "Careers"
-# facebook: "Facebook"
-# twitter: "Twitter"
+ careers: "Karrierer"
+ facebook: "Facebook"
+ twitter: "Twitter"
# create_a_class: "Create a Class"
# other: "Other"
# learn_to_code: "Learn to Code!"
# toggle_nav: "Toggle navigation"
-# jobs: "Jobs"
-# schools: "Schools"
+ jobs: "Jobber"
+ schools: "Skoler"
# educator_wiki: "Educator Wiki"
-# get_involved: "Get Involved"
+ get_involved: "Bli involvert"
# open_source: "Open source (GitHub)"
# support: "Support"
# faqs: "FAQs"
@@ -148,7 +148,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
play:
play_as: "Spill som" # Ladder page
-# compete: "Compete!" # Course details page
+ compete: "Konkurrer"
spectate: "Vær tilskuer" # Ladder page
players: "Spillere" # Hover over a level on /play
hours_played: "Timer spilt" # Hover over a level on /play
@@ -182,53 +182,53 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# campaign_old_multiplayer: "(Deprecated) Old Multiplayer Arenas"
# campaign_old_multiplayer_description: "Relics of a more civilized age. No simulations are run for these older, hero-less multiplayer arenas."
-# code:
-# if: "if" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
-# else: "else"
-# elif: "else if"
-# while: "while"
-# loop: "loop"
-# for: "for"
+ code: # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
+ if: "hvis"
+ else: "eller"
+ elif: "eller hvis"
+ while: "mens"
+ loop: "løkke"
+ for: "for"
# break: "break"
-# continue: "continue"
+ continue: "fortsett"
# pass: "pass"
-# return: "return"
+ return: "Returner"
# then: "then"
-# do: "do"
-# end: "end"
-# function: "function"
-# def: "define"
-# var: "variable"
-# self: "self"
-# hero: "hero"
-# this: "this"
-# or: "or"
-# "||": "or"
-# and: "and"
-# "&&": "and"
-# not: "not"
-# "!": "not"
-# "=": "assign"
-# "==": "equals"
+ do: "gjør"
+ end: "slutt"
+ function: "funksjon"
+ def: "definer"
+ var: "variabel"
+ self: "selv"
+ hero: "helt"
+ this: "dette"
+ or: "eller"
+ "||": "eller"
+ and: "og"
+ "&&": "og"
+ not: "ikke"
+ "!": "ikke"
+ "=": "tildel"
+ "==": "er lik"
# "===": "strictly equals"
-# "!=": "does not equal"
+ "!=": "er ikke lik"
# "!==": "does not strictly equal"
-# ">": "is greater than"
-# ">=": "is greater than or equal"
-# "<": "is less than"
-# "<=": "is less than or equal"
-# "*": "multiplied by"
-# "/": "divided by"
-# "+": "plus"
-# "-": "minus"
-# "+=": "add and assign"
-# "-=": "subtract and assign"
-# True: "True"
-# true: "true"
-# False: "False"
-# false: "false"
-# undefined: "undefined"
-# null: "null"
+ ">": "større enn"
+ ">=": "er større enn eller lik"
+ "<": "mindre enn"
+ "<=": "er mindre enn eller lik"
+ "*": "multiplisert med"
+ "/": "dividert med"
+ "+": "pluss"
+ "-": "minus"
+ "+=": "legg til og tildel"
+ "-=": "fjern og tildel"
+ True: "Sant"
+ true: "sant"
+ False: "Galt"
+ false: "galt"
+ undefined: "udefinert"
+ null: "null"
# nil: "nil"
# None: "None"
@@ -260,8 +260,8 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
log_in: "logg inn med passord"
required: "Du må være logget inn for å gå dit."
login_switch: "Har du allerede en konto?"
-# school_name: "School Name and City"
-# optional: "optional"
+ school_name: "Navn på skole og by"
+ optional: "Valgfri"
# school_name_placeholder: "Example High School, Springfield, IL"
# or_sign_up_with: "or sign up with"
# connected_gplus_header: "You've successfully connected with Google+!"
@@ -271,7 +271,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# connected_facebook_p: "Finish signing up so you can log in with your Facebook account."
# facebook_exists: "You already have an account associated with Facebook!"
# hey_students: "Students, enter the class code from your teacher."
-# birthday: "Birthday"
+ birthday: "Bursdag"
recover:
recover_account_title: "Tilbakestill Passord"
@@ -307,8 +307,8 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
unwatch: "Ikke se på"
submit_patch: "Lever en Patch"
submit_changes: "Lever Endringer"
-# save_changes: "Save Changes"
-# required_field: "Required field"
+ save_changes: "Lagre endringer"
+ required_field: "Påkrevet felt"
general:
and: "og"
@@ -320,9 +320,9 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
accepted: "Akseptert"
rejected: "Avvist"
withdrawn: "Trukket tilbake"
-# accept: "Accept"
-# reject: "Reject"
-# withdraw: "Withdraw"
+ accept: "Godta"
+ reject: "Avslå"
+ withdraw: "trekke tilbake"
submitter: "Innsender"
submitted: "Levert"
commit_msg: "Commit-melding"
@@ -341,7 +341,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
subject: "Emne"
email: "Epost"
password: "Passord"
-# confirm_password: "Confirm Password"
+ confirm_password: "Godta passord"
message: "Melding"
code: "Kode"
ladder: "Stige"
@@ -360,9 +360,9 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
warrior: "Kriger"
ranger: "Vokter"
wizard: "Trollmann"
-# first_name: "First Name"
-# last_name: "Last Name"
-# username: "Username"
+ first_name: "Fornavn"
+ last_name: "Etternavn"
+ username: "Brukernavn"
units:
second: "sekund"
@@ -381,13 +381,13 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
years: "år"
play_level:
-# level_complete: "Level Complete"
+ level_complete: "Nivået er ferdig"
# completed_level: "Completed Level:"
# course: "Course:"
done: "Ferdig"
-# next_level: "Next Level"
+ next_level: "Neste nivå"
# next_game: "Next game"
-# show_menu: "Show game menu"
+ show_menu: "Vis spill meny"
home: "Hjem" # Not used any more, will be removed soon.
level: "Brett" # Like "Level: Dungeons of Kithgard"
skip: "Hopp over"
@@ -417,7 +417,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
victory_saving_progress: "Lagrer framskritt"
victory_go_home: "Gå Hjem"
victory_review: "Fortell oss mer!"
-# victory_review_placeholder: "How was the level?"
+ victory_review_placeholder: "Hvordan var nivået?"
victory_hour_of_code_done: "Er du ferdig?"
victory_hour_of_code_done_yes: "Ja, jeg er ferdig med min Kodetime!"
victory_experience_gained: "XP Opparbeidet"
@@ -447,11 +447,11 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
time_current: "Nå:"
time_total: "Maks:"
time_goto: "Gå til:"
-# non_user_code_problem_title: "Unable to Load Level"
-# infinite_loop_title: "Infinite Loop Detected"
+ non_user_code_problem_title: "Klarer ikke å laste inn Nivået"
+ infinite_loop_title: "En uendelig løkke oppdaget"
# infinite_loop_description: "The initial code to build the world never finished running. It's probably either really slow or has an infinite loop. Or there might be a bug. You can either try running this code again or reset the code to the default state. If that doesn't fix it, please let us know."
# check_dev_console: "You can also open the developer console to see what might be going wrong."
-# check_dev_console_link: "(instructions)"
+ check_dev_console_link: "(Instuksjoner)"
infinite_loop_try_again: "Prøv igjen"
infinite_loop_reset_level: "Tilbakestill Brett"
infinite_loop_comment_out: "Kommenter ut koden min"
@@ -459,7 +459,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
tip_scrub_shortcut: "Ctrl+[ and Ctrl+] spoler bakover og fremover på tidslinjen." # {change}
tip_guide_exists: "Sjekk Guiden i spillmenyen på toppen av siden for nyttig informasjon."
tip_open_source: "CodeCombat er 100% åpen kildekode!"
-# tip_tell_friends: "Enjoying CodeCombat? Tell your friends about us!"
+ tip_tell_friends: "Liker du CodeCombat? Fortell vennene dine om oss!"
tip_beta_launch: "CodeCombat ble lansert i betautgave i oktober 2013."
tip_think_solution: "Tenk på løsningen, ikke på problemet."
tip_theory_practice: "I teorien er det ikke forskjell på teori og praksis, men i praksis er det. - Yogi Berra"
@@ -499,7 +499,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
tip_google: "Har du et problem du ikke kan løse? Google det!"
tip_adding_evil: "Legger til en klype ondskap!"
# tip_hate_computers: "That's the thing about people who think they hate computers. What they really hate is lousy programmers. - Larry Niven"
-# tip_open_source_contribute: "You can help CodeCombat improve!"
+ tip_open_source_contribute: "Du kan hjelpe CodeCombat med å bli bedre!"
# tip_recurse: "To iterate is human, to recurse divine. - L. Peter Deutsch"
# tip_free_your_mind: "You have to let it all go, Neo. Fear, doubt, and disbelief. Free your mind. - Morpheus"
# tip_strong_opponents: "Even the strongest of opponents always has a weakness. - Itachi Uchiha"
@@ -539,9 +539,9 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
auth_caption: "Lagre din fremgang."
leaderboard:
- view_other_solutions: "Sen andre løsninger" # {change}
+ view_other_solutions: "Se andre løsninger" # {change}
scores: "Poeng"
-# top_players: "Top Players by"
+ top_players: "Beste spillere etter"
day: "I dag"
week: "Denne uken"
all: "All tid"
@@ -584,11 +584,11 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
feature4: "{{gems}} bonusjuveler hver måned!"
feature5: "Videoveiledninger"
feature6: "Premium e-poststøtte"
-# feature7: "Private Clans"
-# feature8: "No ads!"
+ feature7: "Private Klaner"
+ feature8: "Ingen reklamer!"
free: "Gratis"
month: "måned"
-# must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
+ must_be_logged: "Du må være logget inn først. Vennligst lag en bruker eller logg inn fra menyen ovenfor."
subscribe_title: "Abonnér"
unsubscribe: "Si opp abonnement"
confirm_unsubscribe: "Bekreft oppsigelse"
@@ -650,7 +650,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
io_blurb: "Enkelt, men obskurt."
# java_blurb: "(Subscriber Only) Android and enterprise."
status: "Status"
-# hero_type: "Type"
+ hero_type: "Type"
weapons: "Våpen"
weapons_warrior: "Sverd - Kort rekkevidde, ingen magi"
weapons_ranger: "Armbrøst, Gevær - Lang rekkevidde, ingen magi"
@@ -679,7 +679,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# function: "function" # skill types
# method: "method"
# snippet: "snippet"
-# number: "number"
+ number: "Nummer"
# array: "array"
# object: "object"
# string: "string"
@@ -727,8 +727,8 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
about:
# main_title: "If you want to learn to program, you need to write (a lot of) code."
# main_description: "At CodeCombat, our job is to make sure you're doing that with a smile on your face."
-# mission_link: "Mission"
-# team_link: "Team"
+ mission_link: "Oppdrag"
+ team_link: "Lag"
# story_link: "Story"
# press_link: "Press"
# mission_title: "Our mission: make programming accessible to every student on Earth."
@@ -739,7 +739,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
nick_title: "Programmerer" # {change}
nick_blurb: "Motivasjonsguru"
matt_title: "Programmerer" # {change}
-# cat_title: "Game Designer"
+ cat_title: "Spill Designer"
# cat_blurb: "Airbender"
scott_title: "Programmerer" # {change}
scott_blurb: "Den fornuftige"
@@ -747,34 +747,34 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# maka_blurb: "Storyteller"
# rob_title: "Software Engineer"
# rob_blurb: "Codes things and stuff"
-# josh_c_title: "Game Designer"
-# josh_c_blurb: "Designs games"
+ josh_c_title: "Spill Designer"
+ josh_c_blurb: "Designer spill"
# robin_title: "UX Design & Research"
# robin_blurb: "Scaffolding"
-# josh_title: "Game Designer"
-# josh_blurb: "Floor Is Lava"
+ josh_title: "Spill Designer"
+ josh_blurb: "Gulvet er Lava"
# phoenix_title: "Software Engineer"
# nolan_title: "Territory Manager"
# elliot_title: "Partnership Manager"
# retrostyle_title: "Illustration"
retrostyle_blurb: "RetroStyle Games"
-# jose_title: "Music"
+ jose_title: "Musikk"
# jose_blurb: "Taking Off"
# community_title: "...and our open-source community"
# community_subtitle: "Over 450 contributors have helped build CodeCombat, with more joining every week!"
# community_description_1: "CodeCombat is a community project, with hundreds of players volunteering to create levels, contribute to our code to add features, fix bugs, playtest, and even translate the game into 50 languages so far. Employees, contributors and the site gain by sharing ideas and pooling effort, as does the open source community in general. The site is built on numerous open source projects, and we are open sourced to give back to the community and provide code-curious players a familiar project to explore and experiment with. Anyone can join the CodeCombat community! Check out our"
# community_description_link: "contribute page"
-# community_description_2: "for more info."
+ community_description_2: "For mer info."
# number_contributors: "Over 450 contributors have lent their support and time to this project."
-# story_title: "Our story so far"
+ story_title: "Vår historie så langt"
# story_subtitle: "Since 2013, CodeCombat has grown from a mere set of sketches to a living, thriving game."
-# story_statistic_1a: "5,000,000+"
+ story_statistic_1a: "5,000,000+"
# story_statistic_1b: "total players"
# story_statistic_1c: "have started their programming journey through CodeCombat"
# story_statistic_2a: "We’ve been translated into over 50 languages — our players hail from"
-# story_statistic_2b: "200+ countries"
-# story_statistic_3a: "Together, they have written"
-# story_statistic_3b: "1 billion lines of code and counting"
+ story_statistic_2b: "200+ land"
+ story_statistic_3a: "Tilsammen har de skrevet"
+ story_statistic_3b: "1 milliard linjer med kode og det stiger fortsatt"
# story_statistic_3c: "across many different programming languages"
# story_long_way_1: "Though we've come a long way..."
# story_sketch_caption: "Nick's very first sketch depicting a programming game in action."
@@ -789,35 +789,35 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# jobs_benefit_9: "10-year option exercise window"
# jobs_benefit_10: "Maternity leave: 10 weeks paid, next 6 @ 55% salary"
# jobs_benefit_11: "Paternity leave: 10 weeks paid"
-# learn_more: "Learn More"
-# jobs_custom_title: "Create Your Own"
+ learn_more: "Lær mer"
+ jobs_custom_title: "Lag din egen"
# jobs_custom_description: "Are you passionate about CodeCombat but don't see a job listed that matches your qualifications? Write us and show how you think you can contribute to our team. We'd love to hear from you!"
# jobs_custom_contact_1: "Send us a note at"
# jobs_custom_contact_2: "introducing yourself and we might get in touch in the future!"
# contact_title: "Press & Contact"
# contact_subtitle: "Need more information? Get in touch with us at"
-# screenshots_title: "Game Screenshots"
-# screenshots_hint: "(click to view full size)"
+ screenshots_title: "Skjermbilder av spillet"
+ screenshots_hint: "(trykk for å vise i full størrelse)"
# downloads_title: "Download Assets & Information"
-# about_codecombat: "About CodeCombat"
-# logo: "Logo"
-# screenshots: "Screenshots"
+ about_codecombat: "Om CodeCombat"
+ logo: "Logo"
+ screenshots: "Skjermbilder"
# character_art: "Character Art"
# download_all: "Download All"
-# previous: "Previous"
-# next: "Next"
+ previous: "Forrige"
+ next: "Neste"
# location_title: "We're located in downtown SF:"
-# teachers:
-# who_for_title: "Who is CodeCombat for?"
+ teachers:
+ who_for_title: "Hvem er CodeCombat for?"
# who_for_1: "We recommend CodeCombat for students aged 9 and up. No prior programming experience is needed. We've designed CodeCombat to appeal to both boys and girls."
# who_for_2: "Our Courses system allows teachers to set up classrooms, track progress and assign additional content to students through a dedicated interface."
-# more_info_title: "Where can I find more information?"
-# more_info_1: "Our"
+ more_info_title: "Hvor kan jeg finne mer informasjon?"
+ more_info_1: "Vår"
# more_info_2: "teachers forum"
# more_info_3: "is a good place to connect with fellow educators who are using CodeCombat."
-# teachers_quote:
+ teachers_quote:
# name: "Demo Form"
# title: "Request a Demo"
# subtitle: "Get your students started in less than an hour. You'll be able to create a class, add students, and monitor their progress as they learn computer science."
@@ -832,16 +832,16 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# advisor: "Advisor"
# principal: "Principal"
# superintendent: "Superintendent"
-# parent: "Parent"
+ parent: "Forelder"
# purchaser_role_label: "Your Purchaser Role"
# influence_advocate: "Influence/Advocate"
# evaluate_recommend: "Evaluate/Recommend"
# approve_funds: "Approve Funds"
# no_purchaser_role: "No role in purchase decisions"
# organization_label: "Name of School/District"
-# city: "City"
-# state: "State"
-# country: "Country"
+ city: "By"
+ state: "Fylke"
+ country: "Land"
# num_students_help: "How many do you anticipate enrolling in CodeCombat?"
# num_students_default: "Select Range"
# education_level_label: "Education Level of Students"
@@ -900,11 +900,11 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
autosave: "Endringer lagres automatisk"
me_tab: "Meg"
picture_tab: "Bilde"
-# delete_account_tab: "Delete Your Account"
-# wrong_email: "Wrong Email"
-# wrong_password: "Wrong Password"
+ delete_account_tab: "Slett brukeren din"
+ wrong_email: "Feil Email"
+ wrong_password: "Feil Passord"
upload_picture: "Last opp bilde"
-# delete_this_account: "Delete this account permanently"
+ delete_this_account: "Slett denne brukeren permanent"
# reset_progress_tab: "Reset All Progress"
# reset_your_progress: "Clear all your progress and start over"
# god_mode: "God Mode"
@@ -942,7 +942,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
keyboard_shortcuts: "Hurtigtaster"
space: "Mellomrom"
enter: "Enter"
-# press_enter: "press enter"
+ press_enter: "Trykk enter"
escape: "Escape"
shift: "Shift"
run_code: "Kjør koden."
@@ -978,50 +978,50 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# social_slack: "Chat with us in the public CodeCombat Slack channel"
contribute_to_the_project: "Bidra på prosjektet"
-# clans:
-# clan: "Clan"
-# clans: "Clans"
-# new_name: "New clan name"
+ clans:
+ clan: "Klan"
+ clans: "Klaner"
+ new_name: "Nytt klan navn"
# new_description: "New clan description"
-# make_private: "Make clan private"
+ make_private: "Gjør klanen privat"
# subs_only: "subscribers only"
-# create_clan: "Create New Clan"
-# private_preview: "Preview"
-# private_clans: "Private Clans"
-# public_clans: "Public Clans"
-# my_clans: "My Clans"
-# clan_name: "Clan Name"
-# name: "Name"
+ create_clan: "Lag en ny klan"
+ private_preview: "Forhåndsvisning"
+ private_clans: "Private Klaner"
+ public_clans: "Offentlige Klaner"
+ my_clans: "Mine klaner"
+ clan_name: "Klan navn"
+ name: "Navn"
# chieftain: "Chieftain"
-# type: "Type"
-# edit_clan_name: "Edit Clan Name"
+ type: "Type"
+ edit_clan_name: "Endre Klan navn"
# edit_clan_description: "Edit Clan Description"
-# edit_name: "edit name"
-# edit_description: "edit description"
+ edit_name: "Endre navn"
+ edit_description: "Endre Beskrivelsen"
# private: "(private)"
# summary: "Summary"
-# average_level: "Average Level"
+ average_level: "Gjennomsnittlig Nivå"
# average_achievements: "Average Achievements"
-# delete_clan: "Delete Clan"
-# leave_clan: "Leave Clan"
-# join_clan: "Join Clan"
-# invite_1: "Invite:"
-# invite_2: "*Invite players to this Clan by sending them this link."
-# members: "Members"
-# progress: "Progress"
-# not_started_1: "not started"
-# started_1: "started"
-# complete_1: "complete"
+ delete_clan: "Slett Klan"
+ leave_clan: "Forlat Klan"
+ join_clan: "Bli med i en klan"
+ invite_1: "Inviter:"
+ invite_2: "*Inviter spillere til denne klanen ved å sende denne linken."
+ members: "Medlemmer"
+ progress: "Framgang"
+ not_started_1: "Ikke startet"
+ started_1: "Startet"
+ complete_1: "Ferdig"
# exp_levels: "Expand levels"
-# rem_hero: "Remove Hero"
-# status: "Status"
-# complete_2: "Complete"
-# started_2: "Started"
-# not_started_2: "Not Started"
-# view_solution: "Click to view solution."
-# view_attempt: "Click to view attempt."
-# latest_achievement: "Latest Achievement"
-# playtime: "Playtime"
+ rem_hero: "Fjern helt"
+ status: "Status"
+ complete_2: "Ferdig"
+ started_2: "Startet"
+ not_started_2: "Ikke Startet"
+ view_solution: "Trykk for å vise løsningen"
+ view_attempt: "Trykk for å vise et forsøk."
+ latest_achievement: "De nyeste prestasjonene"
+ Playtime: "Spilletid"
# last_played: "Last played"
# leagues_explanation: "Play in a league against other clan members in these multiplayer arena instances."
# track_concepts1: "Track concepts"
@@ -1031,14 +1031,14 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# track_concepts3b: "Track levels completed for each member"
# track_concepts4a: "See your students'"
# track_concepts4b: "See your members'"
-# track_concepts5: "solutions"
+ track_concepts5: "Løsninger"
# track_concepts6a: "Sort students by name or progress"
# track_concepts6b: "Sort members by name or progress"
# track_concepts7: "Requires invitation"
# track_concepts8: "to join"
# private_require_sub: "Private clans require a subscription to create or join."
-# courses:
+ courses:
# course: "Course"
# courses: "courses"
# create_new_class: "Create New Class"
@@ -1052,7 +1052,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# edit_settings: "edit class settings"
# edit_settings1: "Edit Class Settings"
# progress: "Class Progress"
-# add_students: "Add Students"
+ add_students: "Legg til studenter"
# stats: "Statistics"
# total_students: "Total students:"
# average_time: "Average level play time:"
@@ -1060,13 +1060,13 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# average_levels: "Average levels completed:"
# total_levels: "Total levels completed:"
# furthest_level: "Furthest level completed:"
-# students: "Students"
-# students1: "students"
-# concepts: "Concepts"
+ students: "Studenter"
+ students1: "studenter"
+ concepts: "Konsepter"
# levels: "levels"
-# played: "Played"
+ played: "Spilt"
# play_time: "Play time:"
-# completed: "Completed:"
+ completed: "Ferdig:"
# invite_students: "Invite students to join this class."
# invite_link_header: "Link to join course"
# invite_link_p_1: "Give this link to students you would like to have join the course."
@@ -1083,9 +1083,9 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# number_programming_students: "Number of Programming Students"
# number_total_students: "Total Students in School/District"
# enter_number_students: "Enter the number of students you need for this class."
-# name_class: "Name your class"
+ name_class: "Gi et navn til klassen din"
# displayed_course_page: "This will be displayed on the course page for you and your students. It can be changed later."
-# buy: "Buy"
+ buy: "Kjøp"
# purchasing_for: "You are purchasing a license for"
# creating_for: "You are creating a class for"
# for: "for" # Like in 'for 30 students'
@@ -1108,8 +1108,8 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# enter_code1: "Enter unlock code"
# enroll: "Enroll"
# pick_from_classes: "Pick from your current classes"
-# enter: "Enter"
-# or: "Or"
+ enter: "Enter"
+ or: "Eller"
# topics: "Topics"
# hours_content: "Hours of content:"
# get_free: "Get FREE course"
@@ -1131,32 +1131,32 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# edit_details: "Edit class details"
# enrolled_courses: "enrolled in paid courses:"
# purchase_enrollments: "Purchase Enrollments"
-# remove_student: "remove student"
+ remove_student: "fjern student"
# assign: "Assign"
# to_assign: "to assign paid courses."
-# teacher: "Teacher"
-# complete: "Complete"
-# none: "None"
-# save: "Save"
+ teacher: "Lærer"
+ complete: "Ferdig"
+ none: "Ingen"
+ save: "Lagre"
# play_campaign_title: "Play the Campaign"
# play_campaign_description: "You’re ready to take the next step! Explore hundreds of challenging levels, learn advanced programming skills, and compete in multiplayer arenas!"
# create_account_title: "Create an Account"
# create_account_description: "Sign up for a FREE CodeCombat account and gain access to more levels, more programming skills, and more fun!"
# preview_campaign_title: "Preview Campaign"
# preview_campaign_description: "Take a sneak peek at all that CodeCombat has to offer before signing up for your FREE account."
-# arena: "Arena"
-# arena_soon_title: "Arena Coming Soon"
-# arena_soon_description: "We are working on a multiplayer arena for classrooms at the end of"
+ arena: "Arena"
+ arena_soon_title: "Arena kommer Snart"
+ arena_soon_description: "Vi jobber med en multiplayer arena for klasserom i slutten av"
# not_enrolled1: "Not enrolled"
# not_enrolled2: "Ask your teacher to enroll you in the next course."
# next_course: "Next Course"
-# coming_soon1: "Coming soon"
+ coming_soon1: "Kommer snart"
# coming_soon2: "We are hard at work making more courses for you!"
# available_levels: "Available Levels"
# welcome_to_courses: "Adventurers, welcome to Courses!"
-# ready_to_play: "Ready to play?"
-# start_new_game: "Start New Game"
-# play_now_learn_header: "Play now to learn"
+ ready_to_play: "Klar for å spille?"
+ start_new_game: "Start et nytt spill"
+ play_now_learn_header: "Spill nå for å lære"
# play_now_learn_1: "basic syntax to control your character"
# play_now_learn_2: "while loops to solve pesky puzzles"
# play_now_learn_3: "strings & variables to customize actions"
@@ -1167,24 +1167,24 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# ready_for_more_1: "Use gems to unlock new items!"
# ready_for_more_2: "Play through brand new worlds and challenges"
# ready_for_more_3: "Learn even more programming!"
-# saved_games: "Saved Games"
+ saved_games: "Lagrede spill"
# hoc: "Hour of Code"
-# my_classes: "My Classes"
+ my_classes: "Mine klasser"
# class_added: "Class successfully added!"
-# view_class: "view class"
-# view_levels: "view levels"
-# join_class: "Join A Class"
+ view_class: "Vis klasse"
+ view_levels: "Vis nivåene"
+ join_class: "Bli med i en klasse"
# ask_teacher_for_code: "Ask your teacher if you have a CodeCombat class code! If so, enter it below:"
# enter_c_code: ""
# join: "Join"
# joining: "Joining class"
# course_complete: "Course Complete"
-# play_arena: "Play Arena"
-# start: "Start"
+ play_arena: "Spill Arena"
+ start: "Start"
# last_level: "Last Level"
# welcome_to_hoc: "Adventurers, welcome to our Hour of Code!"
-# logged_in_as: "Logged in as:"
-# not_you: "Not you?"
+ logged_in_as: "Logget inn som:"
+ not_you: "Ikke deg?"
# welcome_back: "Hi adventurer, welcome back!"
# continue_playing: "Continue Playing"
# more_options: "More options:"
@@ -1204,19 +1204,19 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# each_student_access: "Each student in a class will get access to Courses 2-4 once they are enrolled in paid courses. You may assign each course to each student individually."
# purchase_now: "Purchase Now"
# enrollments: "enrollments"
-# remove_student1: "Remove Student"
+ remove_student1: "Fjern Student"
# are_you_sure: "Are you sure you want to remove this student from this class?"
# remove_description1: "Student will lose access to this classroom and assigned classes. Progress and gameplay is NOT lost, and the student can be added back to the classroom at any time."
# remove_description2: "The activated paid license will not be returned."
-# keep_student: "Keep Student"
-# removing_user: "Removing user"
+ keep_student: "Behold Student"
+ removing_user: "Fjerner bruker"
# to_join_ask: "To join a class, ask your teacher for an unlock code."
# join_this_class: "Join Class"
# enter_here: ""
# successfully_joined: "Successfully joined"
# click_to_start: "Click here to start taking"
# my_courses: "My Courses"
-# classroom: "Classroom"
+ classroom: "Klasserom"
# use_school_email: "use your school email if you have one"
# unique_name: "a unique name no one has chosen"
# pick_something: "pick something you can remember"
@@ -1225,7 +1225,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# optional_school: "optional - what school do you go to?"
# start_playing: "Start Playing"
# skip_this: "Skip this, I'll create an account later!"
-# welcome: "Welcome"
+ welcome: "Velkommen"
# getting_started: "Getting Started with Courses"
# download_getting_started: "Download Getting Started Guide [PDF]"
# getting_started_1: "Create a new class by clicking the green 'Create New Class' button below."
@@ -1234,7 +1234,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# additional_resources: "Additional Resources"
# additional_resources_1_pref: "Download/print our"
# additional_resources_1_mid: "Course 1"
-# additional_resources_1_mid2: "and"
+ additional_resources_1_mid2: "og"
# additional_resources_1_mid3: "Course 2"
# additional_resources_1_suff: "teacher's guides with solutions for each level."
# additional_resources_2_pref: "Complete our"
@@ -1248,19 +1248,19 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# educator_wiki_pref: "Or check out our new"
# educator_wiki_mid: "educator wiki"
# educator_wiki_suff: "to browse the guide online."
-# your_classes: "Your Classes"
+ your_classes: "Dine klasser"
# no_classes: "No classes yet!"
# create_new_class1: "create new class"
# available_courses: "Available Courses"
# unused_enrollments: "Unused enrollments available:"
# students_access: "All students get access to Introduction to Computer Science for free. One enrollment per student is required to assign them to paid CodeCombat courses. A single student does not need multiple enrollments to access all paid courses."
# active_courses: "active courses"
-# no_students: "No students yet!"
-# add_students1: "add students"
+ no_students: "Ingen studenter enda"
+ add_students1: "Legg til studenter"
# view_edit: "view/edit"
# students_enrolled: "students enrolled"
# students_assigned: "students assigned"
-# length: "Length:"
+ length: "Lengde:"
# title: "Courses" # Flat style redesign
# subtitle: "Review course guidelines, solutions, and levels"
# changelog: "View latest changes to course levels."
@@ -1276,8 +1276,8 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# description: "Description" # ClassroomSettingsModal
# language_select: "Select a language"
# language_cannot_change: "Language cannot be changed once students join a class."
-# learn_p: "Learn Python"
-# learn_j: "Learn JavaScript"
+ learn_p: "Lær Python"
+ learn_j: "Lær Javascript"
# avg_student_exp_label: "Average Student Programming Experience"
# avg_student_exp_desc: "This will help us understand how to pace courses better."
# avg_student_exp_select: "Select the best option"
@@ -1287,23 +1287,23 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# avg_student_exp_advanced: "Advanced - extensive experience with typed code"
# avg_student_exp_varied: "Varied Levels of Experience"
# student_age_range_label: "Student Age Range"
-# student_age_range_younger: "Younger than 6"
-# student_age_range_older: "Older than 18"
+ student_age_range_younger: "Yngre enn 6"
+ student_age_range_older: "Eldre enn 18"
# student_age_range_to: "to"
-# create_class: "Create Class"
-# class_name: "Class Name"
+ create_class: "Lag en klasse"
+ class_name: "Klasse navn"
# teacher_account_restricted: "Your account is a teacher account, and so cannot access student content."
-# teacher:
+ teacher:
# teacher_dashboard: "Teacher Dashboard" # Navbar
-# my_classes: "My Classes"
+ my_classes: "Mine klasser"
# courses: "Courses"
# enrollments: "Enrollments"
-# resources: "Resources"
-# help: "Help"
+ resources: "Ressurser"
+ help: "Hjelp"
# students: "Students" # Shared
# language: "Language"
-# edit_class_settings: "edit class settings"
+ edit_class_settings: "Endre klasse instillinger"
# complete: "Complete"
# access_restricted: "Account Update Required"
# teacher_account_required: "A teacher account is required to access this content."
@@ -1317,9 +1317,9 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# archive_class: "archive class"
# unarchive_class: "unarchive class"
# unarchive_this_class: "Unarchive this class"
-# no_students_yet: "This class has no students yet."
-# add_students: "Add Students"
-# create_new_class: "Create a New Class"
+ no_students_yet: "Denne klassen har ikke noen studenter enda."
+ add_students: "Legg til studenter"
+ create_new_class: "Lag en ny klasse"
# class_overview: "Class Overview" # View Class page
# avg_playtime: "Average level playtime"
# total_playtime: "Total play time"
@@ -1332,7 +1332,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# enroll_student: "Enroll student"
# adding_students: "Adding students"
# course_progress: "Course Progress"
-# not_applicable: "N/A"
+ not_applicable: "N/A"
# edit: "edit"
# remove: "remove"
# latest_completed: "Latest Completed"
@@ -1358,7 +1358,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# guides_coming_soon: "Guides coming soon!" # Courses
# show_students_from: "Show students from" # Enroll students modal
# enroll_the_following_students: "Enroll the following students"
-# all_students: "All Students"
+ all_students: "Alle studenter"
# enroll_students: "Enroll Students"
# not_enough_enrollments: "Not enough Enrollments available."
# enrollments_blurb_1: "Students taking Computer Science" # Enrollments page
@@ -1368,9 +1368,9 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# total_enrolled_students: "Total Enrolled Students"
# unenrolled_students: "Unenrolled Students"
# add_enrollment_credits: "Add Enrollment Credits"
-# purchasing: "Purchasing..."
-# purchased: "Purchased!"
-# purchase_now: "Purchase Now"
+ purchasing: "Kjøper..."
+ purchased: "Kjøpt!"
+ purchase_now: "Kjøp nå"
# how_to_enroll: "How to Enroll Students"
# how_to_enroll_blurb_1: "If a student is not enrolled yet, there will be an \"Enroll\" button next to their course progress in your class."
# how_to_enroll_blurb_2: "To bulk-enroll multiple students, select them using the checkboxes on the left side of the classroom page and click the \"Enroll Selected Students\" button."
@@ -1398,7 +1398,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
ambassador_title: "Ambassadør"
ambassador_title_description: "(Brukerstøtte)"
ambassador_summary: "Temm våre forumbrukere og tilby hjelp for de med spørsmål. Våre ambassadører representerer CodeCombat ute i verden."
-# teacher_title: "Teacher"
+ teacher_title: "Lærer"
editor:
main_title: "CodeCombat Editorer"
@@ -1415,8 +1415,8 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
indoor: "Innendørs"
desert: "Ørken"
grassy: "Gresset"
-# mountain: "Mountain"
-# glacier: "Glacier"
+ mountain: "Fjell"
+ glacier: "Isbre"
small: "Liten"
large: "Stor"
fork_title: "Lag ny forgrening"
@@ -1492,8 +1492,8 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
edit_btn_preview: "Forhåndsvis"
edit_article_title: "Rediger Artikkel"
-# polls:
-# priority: "Priority"
+ polls:
+ priority: "Prioritet"
contribute:
page_title: "Bidra"
@@ -1511,7 +1511,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
join_desc_3: ", eller finn oss i vårt "
join_desc_4: "så tar vi det derfra!"
join_url_email: "Send oss en epost"
-# join_url_slack: "public Slack channel"
+ join_url_slack: "Offentlig Slack kanal"
archmage_subscribe_desc: "Få epost om nye muligheter til å kode og kunngjøringer."
artisan_introduction_pref: "Vi må konstruere flere nye brett! Folk skriker etter mer innhold, og vi klarer bare å bygge så mange selv. Akkurat nå er arbeidsverktøyet ditt bare på nivå 1; brett-editoren vår er bare såvidt brukbar, selv for de som har laget den, så vær forberedt. Hvis du har visjoner om kampanjer med alt fra for-løkker til"
artisan_introduction_suf: ", da er denne klassen kanskje for deg."
@@ -1611,7 +1611,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
watch_victory: "Se seieren"
defeat_the: "Overvinn"
# watch_battle: "Watch the battle"
-# tournament_started: ", started"
+ tournament_started: ", startet"
tournament_ends: "Turneringen slutter"
tournament_ended: "Turneringen sluttet "
tournament_rules: "Turneringsregler"
@@ -1622,12 +1622,12 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
tournament_blurb_blog: "bloggen vår"
rules: "Regler"
winners: "Vinnere"
-# league: "League"
+ league: "Liga"
# red_ai: "Red CPU" # "Red AI Wins", at end of multiplayer match playback
# blue_ai: "Blue CPU"
-# wins: "Wins" # At end of multiplayer match playback
-# humans: "Red" # Ladder page display team name
-# ogres: "Blue"
+ wins: "Vinner"
+ humans: "Rød"
+ ogres: "Blå"
user:
stats: "Statistikk"
@@ -1643,7 +1643,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
no_achievements: "Ingen prestasjoner oppnådd ennå."
favorite_prefix: "Favorittspråket er "
favorite_postfix: "."
-# not_member_of_clans: "Not a member of any clans yet."
+ not_member_of_clans: "Ikke et medlem av en klan enda."
achievements:
last_earned: "Sist oppnådd"
@@ -1679,8 +1679,8 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
status_unsubscribed_active: "Du har ikke et aktivt abonnement, og vil ikke bli fakturert, men kontoen din er aktiv enn så lenge."
status_unsubscribed: "Få tilgang til nye nivåer, helter, gjenstander, og bonus gems med et CodeCombat abonnement!"
-# account_invoices:
-# amount: "Amount in US dollars"
+ account_invoices:
+ amount: "Prisen er i Amerikanske dollar"
# declined: "Your card was declined"
# invalid_amount: "Please enter a US dollar amount."
# not_logged_in: "Log in or create an account to access invoices."
@@ -1689,14 +1689,14 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# retrying: "Server error, retrying."
# success: "Successfully paid. Thanks!"
-# account_prepaid:
+ account_prepaid:
# purchase_code: "Purchase a Subscription Code"
# purchase_code1: "Subscription Codes can be redeemed to add premium subscription time to one or more CodeCombat accounts."
# purchase_code2: "Each CodeCombat account can only redeem a particular Subscription Code once."
# purchase_code3: "Subscription Code months will be added to the end of any existing subscription on the account."
-# users: "Users"
-# months: "Months"
-# purchase_total: "Total"
+ users: "Brukere"
+ months: "Måneder"
+ purchase_total: "Total"
# purchase_button: "Submit Purchase"
# your_codes: "Your Codes"
# redeem_codes: "Redeem a Subscription Code"
@@ -1704,17 +1704,17 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# lookup_code: "Lookup prepaid code"
# apply_account: "Apply to your account"
# copy_link: "You can copy the code's link and send it to someone."
-# quantity: "Quantity"
+ quantity: "Mengde"
# redeemed: "Redeemed"
-# no_codes: "No codes yet!"
-# you_can1: "You can"
+ no_codes: "Ingen kode enda!"
+ you_can1: "Du kan"
# you_can2: "purchase a prepaid code"
# you_can3: "that can be applied to your own account or given to others."
-# coppa_deny:
-# text1: "Can’t wait to learn programming?"
-# text2: "Ask your parents to create an account for you!"
-# close: "Close Window"
+ coppa_deny:
+ text1: "Kan du ikke vente med å lære å programmere?"
+ text2: "Spørr foreldrene dine om å lage en bruker for deg!"
+ close: "Lukk Vinduet"
loading_error:
could_not_load: "Feil ved lasting fra server"
@@ -1733,7 +1733,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
bad_input: "Feil i inndata"
server_error: "Serverfeil."
unknown: "Ukjent feil." # {change}
-# error: "ERROR"
+ error: "ERROR"
# general_desc: "Something went wrong, and it’s probably our fault. Try waiting a bit and then refreshing the page, or visit one of the following links to get back to programming!"
resources:
@@ -1744,7 +1744,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
systems: "Systemer"
component: "Komponent"
components: "Komponenter"
-# hero: "Hero"
+ hero: "Helt"
campaigns: "Kampanjer"
# concepts:
@@ -1775,7 +1775,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
delta:
added: "Opprettet"
modified: "Endret"
-# not_modified: "Not Modified"
+ not_modified: "Ikke modifisert"
deleted: "Slettet"
moved_index: "Endret Index"
text_diff: "Tekst Diff"
@@ -1869,19 +1869,19 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# license: "license"
# oreilly: "ebook of your choice"
-# calendar:
-# year: "Year"
-# day: "Day"
-# month: "Month"
-# january: "January"
-# february: "February"
-# march: "March"
-# april: "April"
-# may: "May"
-# june: "June"
-# july: "July"
-# august: "August"
-# september: "September"
-# october: "October"
-# november: "November"
-# december: "December"
+ calendar:
+ year: "år"
+ day: "Dag"
+ month: "Måned"
+ january: "Januar"
+ february: "Februar"
+ march: "Mars"
+ april: "April"
+ may: "Mai"
+ june: "Juni"
+ july: "Juli"
+ august: "August"
+ september: "September"
+ october: "Oktober"
+ november: "November"
+ december: "Desember"
diff --git a/app/locale/nl-NL.coffee b/app/locale/nl-NL.coffee
index 6ae868591..313b08616 100644
--- a/app/locale/nl-NL.coffee
+++ b/app/locale/nl-NL.coffee
@@ -6,7 +6,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
play: "Speel" # The big play button that opens up the campaign view.
play_campaign_version: "Speel de Verhaallijn" # Shows up under big play button if you only play /courses
old_browser: "uh-oh, jouw browser is te oud om CodeCombat te kunnen spelen, Sorry!" # Warning that shows up on really old Firefox/Chrome/Safari
- old_browser_suffix: "Je kan alsnog proberen, maar het zal waarschijnlijk niet werken!"
+ old_browser_suffix: "Je kan het alsnog proberen, maar het zal waarschijnlijk niet werken!"
ipad_browser: "Slecht nieuws: CodeCombat draait niet in je browser op de iPad. Goed nieuws: onze iPad-app wordt op het moment beoordeeld door Apple."
campaign: "Verhaallijn"
for_beginners: "Voor Beginners"
@@ -16,7 +16,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
new_home:
slogan: "Het meest uitdagende spel om mee te leren programmeren."
- classroom_edition: "Klas versie:"
+ classroom_edition: "Klasversie:"
learn_to_code: "Leer programmeren:"
teacher: "Leraar"
student: "Leerling"
@@ -48,10 +48,10 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
kind_of_struggle: "het soort worsteling dat uitmondt in een leerproces dat uitdagend is en "
motivating: "motiveert"
not_tedious: "niet vervelend."
- gaming_is_good: "Studies geven aan dat speels leren goed is voor de hersenen van kinderen. (En dat klopt!)"
+ gaming_is_good: "Studies wijzen uit dat speels leren goed is voor de hersenen van kinderen. (En dat klopt!)"
game_based: "Wanneer spel-gebaseerde leersystemen worden"
compared: "vergeleken"
- conventional: "met conventionele assessment methodes, is het verschil duidelijk: games zijn beter in het stimuleren van leerlingen bij het onthouden van kennis, concentratie en het"
+ conventional: "met conventionele assessment methoden, is het verschil duidelijk: games zijn beter in het stimuleren van leerlingen bij het onthouden van kennis, concentratie en het"
perform_at_higher_level: "niveau van hun prestaties."
feedback: "Games verschaffen directe feedback, wat leerlingen in staat stelt hun oplossingen te verbeteren en zij een holistisch begrip van de concepten krijgen, in plaats van beperkt zijn tot antwoorden als “correct” of “incorrect”."
real_game: "Een echt spel, wat je speelt met echte programmeertaal."
@@ -1288,11 +1288,11 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
avg_student_exp_varied: "Verschilt enorm per leerling"
student_age_range_label: "Leeftijdscategorie leerlingen"
student_age_range_younger: "Jonger dan 6"
- student_age_range_older: "Ouden dan 18"
+ student_age_range_older: "Ouder dan 18"
student_age_range_to: "tot"
create_class: "Maak klas aan"
class_name: "Klasnaam"
- teacher_account_restricted: "Jouw account is een Docenten Account, daarom heeft dit account geen toegang tot inhoud bedoelt voor leerlingen."
+ teacher_account_restricted: "Jouw account is een Docenten Account, daarom heeft dit account geen toegang tot inhoud bedoeld voor leerlingen."
teacher:
teacher_dashboard: "Docent Dashboard" # Navbar
@@ -1312,7 +1312,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
teacher_account_explanation: "Een CodeCombat Docenten account geeft je de mogelijkheid om klassen aan te maken, voortgang van leerlingen te bekijken terwijl ze de cursussen volgen, inschrijvingen beheren en hulpmiddelen te gebruiken voor het opzetten van een leerplan"
current_classes: "Huidige Klassen"
archived_classes: "Gearchiveerde Klassen"
- archived_classes_blurb: "Klassen kunnen worden gearchiveerd voor toekomstige refferentie. Dearchiveer een klas om deze weer in de lijst Huidige Klassen te zien"
+ archived_classes_blurb: "Klassen kunnen worden gearchiveerd voor toekomstige referentie. Dearchiveer een klas om deze weer in de lijst Huidige Klassen te zien"
view_class: "bekijk klas"
archive_class: "archiveer klas"
unarchive_class: "dearchiveer klas"
@@ -1372,9 +1372,9 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
purchased: "Aanschaffing Voltooid!"
purchase_now: "Nu Aanschaffen"
how_to_enroll: "Hoe Schrijf ik Leerlingen in"
- how_to_enroll_blurb_1: "Als een leerling nog niet is ingeschreven zal er een \"Enroll\" knop naast hun cursus voortgang zijn in je klas."
- how_to_enroll_blurb_2: "Om meerdere leerlingen in bulk in te schrijven, selecteer ze door middel van de selectievakjes aan de linkerkant van de klas pagina en klik op de \"Enroll Selected Students\" knop."
- how_to_enroll_blurb_3: "Zodra een leerling is ingeschreven zal deze toegang hebben tot alle inhoud van de cursus."
+ how_to_enroll_blurb_1: "Als een leerling nog niet is ingeschreven, staat er een knop genaamd \"Enroll\" bij hun naam."
+ how_to_enroll_blurb_2: "Je kunt meerdere leerlingen tegelijkertijd inschrijven. Vink simpelweg de vakjes aan voor de leerlingen die je wilt inschrijven en klik op de knop \"Enroll Selected Students\"."
+ how_to_enroll_blurb_3: "Zodra een leerling is ingeschreven heeft hij/zij toegang tot alle inhoud."
bulk_pricing_blurb: "Aanschaffen voor meer dan 25 leerlingen? Neem contact met ons op."
total_unenrolled: "Totaal aantal niet ingeschreven"
export_student_progress: "Exporteer Voortgang Leerlingen (CSV bestand)"
diff --git a/app/locale/ru.coffee b/app/locale/ru.coffee
index ebd7b51da..95e5d6f1d 100644
--- a/app/locale/ru.coffee
+++ b/app/locale/ru.coffee
@@ -38,7 +38,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
accessible_to: "Доступно для"
everyone: "каждого"
democratizing: "Демократизация процесса обучения программированию лежит в основе нашей философии. Каждый должен иметь возможность обучаться программированию."
-# forgot_learning: "I think they actually forgot that they were actually learning something."
+ forgot_learning: "Я думаю, что они забудут, что они действительно что-то изучают."
# wanted_to_do: " Coding is something I've always wanted to do, and I never thought I would be able to learn it in school."
# why_games: "Why is learning through games important?"
# games_reward: "Games reward the productive struggle."
diff --git a/app/models/GameUIState.coffee b/app/models/GameUIState.coffee
new file mode 100644
index 000000000..6d86e8379
--- /dev/null
+++ b/app/models/GameUIState.coffee
@@ -0,0 +1,29 @@
+CocoModel = require './CocoModel'
+
+module.exports = class GameUIState extends CocoModel
+ @className: 'GameUIState'
+ @schema: {
+ type: 'object'
+ properties: {
+
+ canDragCamera: {
+ type: 'boolean'
+ description: 'Serves as a lock to enable or disable camera movement.'
+ }
+
+ selected: {
+ # TODO: Turn this into a collection which can be listened to? With Thang models.
+ type: 'object'
+ description: 'Array of selected thangs'
+ properties: {
+ sprite: { description: 'Lank instance' }
+ thang: { description: 'Thang object generated by the world' }
+ }
+ }
+ }
+ }
+
+ defaults: -> {
+ selected: []
+ canDragCamera: true
+ }
diff --git a/app/schemas/subscriptions/god.coffee b/app/schemas/subscriptions/god.coffee
index e025c97c4..1c59ec44f 100644
--- a/app/schemas/subscriptions/god.coffee
+++ b/app/schemas/subscriptions/god.coffee
@@ -52,6 +52,7 @@ module.exports =
overallStatus: {type: ['string', 'null'], enum: ['success', 'failure', 'incomplete', null]}
totalFrames: {type: ['integer', 'undefined']}
lastFrameHash: {type: ['number', 'undefined']}
+ simulationFrameRate: {type: ['number', 'undefined']}
'god:world-load-progress-changed': c.object {required: ['progress', 'god']},
god: {type: 'object'}
diff --git a/app/templates/about.jade b/app/templates/about.jade
index 619dc241b..b66801346 100644
--- a/app/templates/about.jade
+++ b/app/templates/about.jade
@@ -387,7 +387,7 @@ block content
b CodeCombat Inc.
p 301 Howard Street
p Suite No. 830
- p San Francisco, California 94015
+ p San Francisco, California 94105
a(href="mailto:team@codecombat.com") team@codecombat.com
.col-sm-4
p
diff --git a/app/templates/admin/school-counts.jade b/app/templates/admin/school-counts.jade
index 556330d84..fad5b8edb 100644
--- a/app/templates/admin/school-counts.jade
+++ b/app/templates/admin/school-counts.jade
@@ -6,16 +6,30 @@ block content
if !me.isAdmin()
div You must be logged in as an admin to view this page.
-
+ else if !view.countryGraphs || !view.countryGraphs['USA']
+ h3 Loading...
else
- p CodeCombat is now in #{view.totalSchools} schools with #{view.totalStudents} students [and #{view.totalTeachers} teachers] [in #{view.totalStates} states]
- p Students not attached to NCES data: #{view.untriagedStudents}
+ p
+ div CodeCombat is now in #{view.countryGraphs['USA'].totalSchools} schools with #{view.countryGraphs['USA'].totalStudents} students [and #{view.countryGraphs['USA'].totalTeachers} teachers] [in #{view.countryGraphs['USA'].totalStates} states] in the USA
+ p
+ div Untriaged students: #{view.untriagedStudents}
+ div Untriaged teachers: #{view.untriagedTeachers}
.small Teacher: owns a classroom or has a teacher role
- .small Student: member of a classroom or has schoolName set
- .small States, Districts, Schools are from NCES
+ .small Student: member of a classroom or has schoolName set, not in HoC course instance
+ .small +3 USA states are GU, PR, DC
- h2 State Counts
- if view.stateCounts
+ p
+ ul
+ li
+ a(href="#usaStates") USA States
+ li
+ a(href="#usaDistrictsByState") USA Districts by State
+ li
+ a(href="#countries") Countries
+
+ a(name="usaStates")
+ h2 USA States
+ if view.countryGraphs['USA'].stateCounts
table.table.table-striped.table-condensed
tr
th State
@@ -23,7 +37,7 @@ block content
th Schools
th Teachers
th Students
- each stateCount in view.stateCounts
+ each stateCount in view.countryGraphs['USA'].stateCounts
tr
td= stateCount.state
td= stateCount.districts
@@ -31,8 +45,9 @@ block content
td= stateCount.teachers
td= stateCount.students
- h2 District Counts by State
- if view.districtCounts
+ a(name="usaDistrictsByState")
+ h2 USA Districts by State
+ if view.countryGraphs['USA'].districtCounts
table.table.table-striped.table-condensed
tr
th State
@@ -40,10 +55,26 @@ block content
th Schools
th Teachers
th Students
- each districtCount in view.districtCounts
+ each districtCount in view.countryGraphs['USA'].districtCounts
tr
td= districtCount.state
td= districtCount.district
td= districtCount.schools
td= districtCount.teachers
td= districtCount.students
+
+ a(name="countries")
+ h2 Countries
+ if view.countryCounts
+ table.table.table-striped.table-condensed
+ tr
+ th Country
+ th Schools
+ th Teachers
+ th Students
+ each countryCount in view.countryCounts
+ tr
+ td= countryCount.country
+ td= countryCount.schools
+ td= countryCount.teachers
+ td= countryCount.students
diff --git a/app/templates/editor/verifier/verifier-view.jade b/app/templates/editor/verifier/verifier-view.jade
index 39efc524c..02db75e34 100644
--- a/app/templates/editor/verifier/verifier-view.jade
+++ b/app/templates/editor/verifier/verifier-view.jade
@@ -105,6 +105,14 @@ block content
h4.test-failed User Code Problems
pre.test-failed #{JSON.stringify(test.userCodeProblems, null, 2)}
+ if test.simulationFrameRate
+ if test.simulationFrameRate > 90
+ div.test-success ✓ #{test.simulationFrameRate.toFixed(1)} FPS
+ else if test.simulationFrameRate > 30
+ div.test-running ~ #{test.simulationFrameRate.toFixed(1)} FPS
+ else
+ div.test-failed ✘ #{test.simulationFrameRate.toFixed(1)} FPS
+
else
h1 Loading Level...
diff --git a/app/views/admin/SchoolCountsView.coffee b/app/views/admin/SchoolCountsView.coffee
index 0ff795921..28e7e7b5a 100644
--- a/app/views/admin/SchoolCountsView.coffee
+++ b/app/views/admin/SchoolCountsView.coffee
@@ -1,10 +1,11 @@
RootView = require 'views/core/RootView'
CocoCollection = require 'collections/CocoCollection'
Classroom = require 'models/Classroom'
+CourseInstance = require 'models/CourseInstance'
TrialRequest = require 'models/TrialRequest'
User = require 'models/User'
+utils = require 'core/utils'
-# TODO: trim orphaned students: course instances != Single Player, hourOfCode != true
# TODO: match anonymous trial requests with real users via email
module.exports = class SchoolCountsView extends RootView
@@ -15,6 +16,8 @@ module.exports = class SchoolCountsView extends RootView
return super() unless me.isAdmin()
@classrooms = new CocoCollection([], { url: "/db/classroom/-/users", model: Classroom })
@supermodel.loadCollection(@classrooms, 'classrooms', {cache: false})
+ @courseInstances = new CocoCollection([], { url: "/db/course_instance/-/non-hoc", model: CourseInstance})
+ @supermodel.loadCollection(@courseInstances, 'course-instances', {cache: false})
@students = new CocoCollection([], { url: "/db/user/-/students", model: User })
@supermodel.loadCollection(@students, 'students', {cache: false})
@teachers = new CocoCollection([], { url: "/db/user/-/teachers", model: User })
@@ -30,41 +33,44 @@ module.exports = class SchoolCountsView extends RootView
teacherMap = {} # Used to make sure teachers and students only counted once
studentMap = {} # Used to make sure teachers and students only counted once
+ studentNonHocMap = {} # Used to exclude HoC users
teacherStudentMap = {} # Used to link students to their teacher locations
- orphanedSchoolStudentMap = {} # Used to link student schoolName to teacher Nces data
countryStateDistrictSchoolCountsMap = {} # Data graph
- console.log(new Date().toISOString(), 'Processing classrooms...')
+ console.log(new Date().toISOString(), "Processing #{@courseInstances.models.length} course instances...")
+ for courseInstance in @courseInstances.models
+ studentNonHocMap[courseInstance.get('ownerID')] = true
+ studentNonHocMap[studentID] = true for studentID in courseInstance.get('members') ? []
+
+ console.log(new Date().toISOString(), "Processing #{@classrooms.models.length} classrooms...")
for classroom in @classrooms.models
teacherID = classroom.get('ownerID')
teacherMap[teacherID] ?= {}
teacherMap[teacherID] = true
teacherStudentMap[teacherID] ?= {}
for studentID in classroom.get('members')
+ continue unless studentNonHocMap[studentID]
studentMap[studentID] = true
teacherStudentMap[teacherID][studentID] = true
- console.log(new Date().toISOString(), 'Processing teachers...')
+ console.log(new Date().toISOString(), "Processing #{@teachers.models.length} teachers...")
for teacher in @teachers.models
teacherMap[teacher.id] ?= {}
delete studentMap[teacher.id]
- console.log(new Date().toISOString(), 'Processing students...')
+ console.log(new Date().toISOString(), "Processing #{@students.models.length} students...")
for student in @students.models when not teacherMap[student.id]
+ continue unless studentNonHocMap[student.id]
schoolName = student.get('schoolName')
studentMap[student.id] = true
- orphanedSchoolStudentMap[schoolName] ?= {}
- orphanedSchoolStudentMap[schoolName][student.id] = true
- console.log(new Date().toISOString(), 'Processing trial requests...')
- # TODO: this step is crazy slow
- orphanSchoolsMatched = 0
- orphanStudentsMatched = 0
+ console.log(new Date().toISOString(), "Processing trial #{@trialRequests.models.length} requests...")
for trialRequest in @trialRequests.models
teacherID = trialRequest.get('applicant')
unless teacherMap[teacherID]
+ # E.g. parents
# console.log("Skipping non-teacher #{teacherID} trial request #{trialRequest.id}")
- continue
+ continue
props = trialRequest.get('properties')
if props.nces_id and props.country and props.state
country = props.country
@@ -78,27 +84,44 @@ module.exports = class SchoolCountsView extends RootView
countryStateDistrictSchoolCountsMap[country][state][district][school].teachers[teacherID] = true
for studentID, val of teacherStudentMap[teacherID]
countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
- for orphanSchool, students of orphanedSchoolStudentMap
- if school is orphanSchool or school.replace(/unified|elementary|high|district|#\d+|isd|unified district|school district/ig, '').trim() is orphanSchool.trim()
- orphanSchoolsMatched++
- for studentID, val of students
- orphanStudentsMatched++
- countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
- delete orphanedSchoolStudentMap[school]
- console.log(new Date().toISOString(), "#{orphanSchoolsMatched} orphanSchoolsMatched #{orphanStudentsMatched} orphanStudentsMatched")
+ else if not _.isEmpty(props.country)
+ country = props.country?.trim()
+ country = country[0].toUpperCase() + country.substring(1).toLowerCase()
+ country = 'Taiwan' if /台灣/ig.test(country)
+ country = 'UK' if /^uk$|united kingdom|england/ig.test(country)
+ country = 'USA' if /^u\.s\.?(\.a)?\.?$|^us$|america|united states|usa/ig.test(country)
+ state = props.state ? 'unknown'
+ if country is 'USA'
+ stateName = utils.usStateCodes.sanitizeStateName(state)
+ state = utils.usStateCodes.getStateCodeByStateName(stateName) if stateName
+ state = utils.usStateCodes.sanitizeStateCode(state) ? state
+ district = 'unknown'
+ school = props.organiziation ? 'unknown'
+ countryStateDistrictSchoolCountsMap[country] ?= {}
+ countryStateDistrictSchoolCountsMap[country][state] ?= {}
+ countryStateDistrictSchoolCountsMap[country][state][district] ?= {}
+ countryStateDistrictSchoolCountsMap[country][state][district][school] ?= {students: {}, teachers: {}}
+ countryStateDistrictSchoolCountsMap[country][state][district][school].teachers[teacherID] = true
+ for studentID, val of teacherStudentMap[teacherID]
+ countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
- console.log(new Date().toISOString(), 'Building graph...')
- @totalSchools = 0
- @totalStudents = 0
- @totalTeachers = 0
- @totalStates = 0
- @stateCounts = []
- stateCountsMap = {}
- @districtCounts = []
+ console.log(new Date().toISOString(), 'Building country graphs...')
+ @countryGraphs = {}
+ @countryCounts = []
+ totalStudents = 0
+ totalTeachers = 0
for country, stateDistrictSchoolCountsMap of countryStateDistrictSchoolCountsMap
- continue unless /usa/ig.test(country)
+ @countryGraphs[country] =
+ districtCounts: []
+ stateCounts: []
+ stateCountsMap: {}
+ totalSchools: 0
+ totalStates: 0
+ totalStudents: 0
+ totalTeachers: 0
for state, districtSchoolCountsMap of stateDistrictSchoolCountsMap
- @totalStates++
+ if utils.usStateCodes.sanitizeStateCode(state)? or ['GU', 'PR'].indexOf(state) >= 0
+ @countryGraphs[country].totalStates++
stateData = {state: state, districts: 0, schools: 0, students: 0, teachers: 0}
for district, schoolCountsMap of districtSchoolCountsMap
stateData.districts++
@@ -106,39 +129,40 @@ module.exports = class SchoolCountsView extends RootView
for school, counts of schoolCountsMap
studentCount = Object.keys(counts.students).length
teacherCount = Object.keys(counts.teachers).length
- @totalSchools++
- @totalStudents += studentCount
- @totalTeachers += teacherCount
+ @countryGraphs[country].totalSchools++
+ @countryGraphs[country].totalStudents += studentCount
+ @countryGraphs[country].totalTeachers += teacherCount
stateData.schools++
stateData.students += studentCount
stateData.teachers += teacherCount
districtData.schools++
districtData.students += studentCount
districtData.teachers += teacherCount
- @districtCounts.push(districtData)
- @stateCounts.push(stateData)
- stateCountsMap[state] = stateData
- @untriagedStudents = Object.keys(studentMap).length - @totalStudents
+ @countryGraphs[country].districtCounts.push(districtData)
+ @countryGraphs[country].stateCounts.push(stateData)
+ @countryGraphs[country].stateCountsMap[state] = stateData
+ @countryCounts.push
+ country: country
+ schools: @countryGraphs[country].totalSchools
+ students: @countryGraphs[country].totalStudents
+ teachers: @countryGraphs[country].totalTeachers
+ totalStudents += @countryGraphs[country].totalSchools
+ totalTeachers += @countryGraphs[country].totalTeachers
+ @untriagedStudents = Object.keys(studentMap).length - totalStudents
+ @untriagedTeachers = Object.keys(teacherMap).length - totalTeachers
- @stateCounts.sort (a, b) ->
- return -1 if a.students > b.students
- return 1 if a.students < b.students
- return -1 if a.teachers > b.teachers
- return 1 if a.teachers < b.teachers
- return -1 if a.districts > b.districts
- return 1 if a.districts < b.districts
- b.state.localeCompare(a.state)
- @districtCounts.sort (a, b) ->
- if a.state isnt b.state
- return -1 if stateCountsMap[a.state].students > stateCountsMap[b.state].students
- return 1 if stateCountsMap[a.state].students < stateCountsMap[b.state].students
- return -1 if stateCountsMap[a.state].teachers > stateCountsMap[b.state].teachers
- return 1 if stateCountsMap[a.state].teachers < stateCountsMap[b.state].teachers
- a.state.localeCompare(b.state)
- else
- return -1 if a.students > b.students
- return 1 if a.students < b.students
- return -1 if a.teachers > b.teachers
- return 1 if a.teachers < b.teachers
- a.district.localeCompare(b.district)
+ for country, graph of @countryGraphs
+ graph.stateCounts.sort (a, b) ->
+ b.students - a.students or b.teachers - a.teachers or b.schools - a.schools or b.districts - a.districts or b.state.localeCompare(a.state)
+ graph.districtCounts.sort (a, b) ->
+ if a.state isnt b.state
+ stateCountsA = graph.stateCountsMap[a.state]
+ stateCountsB = graph.stateCountsMap[b.state]
+ stateCountsB.students - stateCountsA.students or stateCountsB.teachers - stateCountsA.teachers or stateCountsB.schools - stateCountsA.schools or stateCountsB.districts - stateCountsA.districts or a.state.localeCompare(b.state)
+ else
+ b.students - a.students or b.teachers - a.teachers or b.schools - a.schools or b.district.localeCompare(a.district)
+ @countryCounts.sort (a, b) ->
+ b.students - a.students or b.teachers - a.teachers or b.schools - a.schools or b.country.localeCompare(a.country)
+
+ console.log(new Date().toISOString(), 'Done...')
super()
diff --git a/app/views/editor/level/thangs/ThangsTabView.coffee b/app/views/editor/level/thangs/ThangsTabView.coffee
index c5ff8c65f..4dd28b836 100644
--- a/app/views/editor/level/thangs/ThangsTabView.coffee
+++ b/app/views/editor/level/thangs/ThangsTabView.coffee
@@ -11,6 +11,7 @@ Thang = require 'lib/world/thang'
LevelThangEditView = require './LevelThangEditView'
ComponentsCollection = require 'collections/ComponentsCollection'
require 'vendor/treema'
+GameUIState = require 'models/GameUIState'
# Moving the screen while dragging thangs constants
MOVE_MARGIN = 0.15
@@ -29,7 +30,6 @@ module.exports = class ThangsTabView extends CocoView
template: thangs_template
subscriptions:
- 'surface:sprite-selected': 'onExtantThangSelected'
'surface:mouse-moved': 'onSurfaceMouseMoved'
'surface:mouse-over': 'onSurfaceMouseOver'
'surface:mouse-out': 'onSurfaceMouseOut'
@@ -39,7 +39,6 @@ module.exports = class ThangsTabView extends CocoView
'editor:view-switched': 'onViewSwitched'
'sprite:dragged': 'onSpriteDragged'
'sprite:mouse-up': 'onSpriteMouseUp'
- 'sprite:mouse-down': 'onSpriteMouseDown'
'sprite:double-clicked': 'onSpriteDoubleClicked'
'surface:stage-mouse-down': 'onStageMouseDown'
'surface:stage-mouse-up': 'onStageMouseUp'
@@ -78,12 +77,17 @@ module.exports = class ThangsTabView extends CocoView
constructor: (options) ->
super options
@world = options.world
+ @gameUIState = new GameUIState()
+ @listenTo(@gameUIState, 'sprite:mouse-down', @onSpriteMouseDown)
+ @listenTo(@gameUIState, 'surface:stage-mouse-move', @onStageMouseMove)
+ @listenTo(@gameUIState, 'change:selected', @onChangeSelected)
# should load depended-on Components, too
@thangTypes = @supermodel.loadCollection(new ThangTypeSearchCollection(), 'thangs').model
# just loading all Components for now: https://github.com/codecombat/codecombat/issues/405
@componentCollection = @supermodel.loadCollection(new ComponentsCollection(), 'components').load()
@level = options.level
+ @onThangsChanged = _.debounce(@onThangsChanged)
$(document).bind 'contextmenu', @preventDefaultContextMenu
@@ -203,7 +207,7 @@ module.exports = class ThangsTabView extends CocoView
initSurface: ->
webGLCanvas = $('canvas#webgl-surface', @$el)
normalCanvas = $('canvas#normal-surface', @$el)
- @surface = new Surface @world, normalCanvas, webGLCanvas, {
+ @surface = new Surface(@world, normalCanvas, webGLCanvas, {
paths: false
coords: true
grid: true
@@ -212,7 +216,9 @@ module.exports = class ThangsTabView extends CocoView
showInvisible: true
frameRate: 15
levelType: @level.get 'type', true
- }
+ @gameUIState
+ handleEvents: false
+ })
@surface.playing = false
@surface.setWorld @world
@surface.lankBoss.suppressSelectionSounds = true
@@ -240,38 +246,79 @@ module.exports = class ThangsTabView extends CocoView
@selectAddThang null, true
@surface?.lankBoss?.selectLank null, null
- onSpriteMouseDown: (e) ->
- @dragged = false
- # Sprite clicks happen after stage clicks, but we need to know whether a sprite is being clicked.
- # clearTimeout @backgroundAddClickTimeout
- # if e.originalEvent.nativeEvent.button == 2
- # @onSpriteContextMenu e
-
onStageMouseDown: (e) ->
- return unless @addThangLank?.thangType.get('kind') is 'Wall'
- @surface.camera.dragDisabled = true
- @paintingWalls = true
+ # initial values for a mouse click lifecycle
+ @dragged = 0
+ @willUnselectSprite = false
+ @gameUIState.set('canDragCamera', true)
+
+ if @addThangLank?.thangType.get('kind') is 'Wall'
+ @paintingWalls = true
+ @gameUIState.set('canDragCamera', false)
+
+ else if @addThangLank
+ # We clicked on the background when we had an add Thang selected, so add it
+ @addThang @addThangType, @addThangLank.thang.pos
+
+ else if e.onBackground
+ @gameUIState.set('selected', [])
+
+ onStageMouseMove: (e) ->
+ @dragged += 1
onStageMouseUp: (e) ->
- if @paintingWalls
- # We need to stop painting walls, but we may also stop in onExtantThangSelected.
- _.defer =>
- @paintingWalls = @paintedWalls = @surface.camera.dragDisabled = false
- else if @addThangLank
- @surface.camera.lock()
- # If we click on the background, we need to add @addThangLank, but not if onSpriteMouseUp will fire.
- @backgroundAddClickTimeout = _.defer => @onExtantThangSelected {}
+ @paintingWalls = false
$('#contextmenu').hide()
+ onSpriteMouseDown: (e) ->
+ nativeEvent = e.originalEvent.nativeEvent
+ # update selection
+ selected = []
+ if nativeEvent.metaKey or nativeEvent.ctrlKey
+ selected = _.clone(@gameUIState.get('selected'))
+ if e.thang?.isSelectable
+ alreadySelected = _.find(selected, (s) -> s.thang is e.thang)
+ if alreadySelected
+ # move to end (make it the last selected) and maybe unselect it
+ @willUnselectSprite = true
+ selected = _.without(selected, alreadySelected)
+ selected.push({ thang: e.thang, sprite: e.sprite, spellName: e.spellName })
+ if _.any(selected) and key.alt
+ # Clone selected thang instead of selecting it
+ lastSelected = _.last(selected)
+ @selectAddThangType lastSelected.thang.spriteName, lastSelected.thang
+ selected = []
+ @gameUIState.set('selected', selected)
+ if _.any(selected)
+ @gameUIState.set('canDragCamera', false)
+
onSpriteDragged: (e) ->
- return unless @selectedExtantThang and e.thang?.id is @selectedExtantThang?.id
- @dragged = true
- @surface.camera.dragDisabled = true
+ selected = @gameUIState.get('selected')
+ return unless _.any(selected) and @dragged > 10
+ @willUnselectSprite = false
{stageX, stageY} = e.originalEvent
+
+ # move the one under the mouse
+ lastSelected = _.last(selected)
cap = @surface.camera.screenToCanvas x: stageX, y: stageY
wop = @surface.camera.canvasToWorld cap
- wop.z = @selectedExtantThang.depth / 2
- @adjustThangPos @selectedExtantLank, @selectedExtantThang, wop
+ wop.z = lastSelected.thang.depth / 2
+ posBefore = _.clone(lastSelected.thang.pos)
+ @adjustThangPos lastSelected.sprite, lastSelected.thang, wop
+ posAfter = lastSelected.thang.pos
+
+ # move any others selected, proportionally to how the 'main' sprite moved
+ xDiff = posAfter.x - posBefore.x
+ yDiff = posAfter.y - posBefore.y
+ if xDiff or yDiff
+ for singleSelected in selected.slice(0, selected.length - 1)
+ newPos = {
+ x: singleSelected.thang.pos.x + xDiff
+ y: singleSelected.thang.pos.y + yDiff
+ }
+ @adjustThangPos singleSelected.sprite, singleSelected.thang, newPos
+
+ # move the camera if we're on the edge of the screen
[w, h] = [@surface.camera.canvasWidth, @surface.camera.canvasHeight]
sidebarWidths = ((if @$el.find(id).hasClass('hide') then 0 else (@$el.find(id).outerWidth() / @surface.camera.canvasScaleFactorX)) for id in ['#all-thangs', '#add-thangs-view'])
w -= sidebarWidth for sidebarWidth in sidebarWidths
@@ -279,24 +326,30 @@ module.exports = class ThangsTabView extends CocoView
@calculateMovement(cap.x / w, cap.y / h, w / h)
onSpriteMouseUp: (e) ->
- clearTimeout @backgroundAddClickTimeout
- @surface.camera.unlock()
- if e.originalEvent.nativeEvent.button == 2 and @selectedExtantThang
+ selected = @gameUIState.get('selected')
+ if e.originalEvent.nativeEvent.button == 2 and _.any(selected)
@onSpriteContextMenu e
clearInterval(@movementInterval) if @movementInterval?
@movementInterval = null
- @surface.camera.dragDisabled = false
- return unless @selectedExtantThang and e.thang?.id is @selectedExtantThang?.id
- pos = @selectedExtantThang.pos
-
- thang = _.find(@level.get('thangs') ? [], {id: @selectedExtantThang.id})
- path = "#{@pathForThang(thang)}/components/original=#{LevelComponent.PhysicalID}"
- physical = @thangsTreema.get path
- return if not physical or (physical.config.pos.x is pos.x and physical.config.pos.y is pos.y)
- @thangsTreema.set path + '/config/pos', x: pos.x, y: pos.y, z: pos.z
+
+ return unless _.any(selected)
+
+ for singleSelected in selected
+ pos = singleSelected.thang.pos
+
+ thang = _.find(@level.get('thangs') ? [], {id: singleSelected.thang.id})
+ path = "#{@pathForThang(thang)}/components/original=#{LevelComponent.PhysicalID}"
+ physical = @thangsTreema.get path
+ continue if not physical or (physical.config.pos.x is pos.x and physical.config.pos.y is pos.y)
+ @thangsTreema.set path + '/config/pos', x: pos.x, y: pos.y, z: pos.z
+
+ if @willUnselectSprite
+ clickedSprite = _.find(selected, {sprite: e.sprite})
+ @gameUIState.set('selected', _.without(selected, clickedSprite))
onSpriteDoubleClicked: (e) ->
- return unless e.thang and not @dragged
+ return if @dragged > 10
+ return unless e.thang
@editThang thangID: e.thang.id
onRandomTerrainGenerated: (e) ->
@@ -320,35 +373,21 @@ module.exports = class ThangsTabView extends CocoView
@onThangsChanged()
@selectAddThangType null
+ onChangeSelected: (gameUIState, selected) ->
+ previousSprite = gameUIState.previousAttributes()?.selected?.sprite
+ sprite = selected?.sprite
+ thang = selected?.thang
- # TODO: figure out a good way to have all Surface clicks and Treema clicks just proxy in one direction, so we can maintain only one way of handling selection and deletion
- onExtantThangSelected: (e) ->
- @selectedExtantLank?.setNameLabel? null unless @selectedExtantLank is e.sprite
- @selectedExtantThang = e.thang
- @selectedExtantLank = e.sprite
- paintedAWall = @paintedWalls
- @paintingWalls = @paintedWalls = @surface.camera.dragDisabled = false
- if paintedAWall
- # Skip adding a wall now, because we already dragged to add one
- null
- else if e.thang and (key.alt or key.meta)
- # We alt-clicked, so create a clone addThang
- @selectAddThangType e.thang.spriteName, @selectedExtantThang
- else if @justAdded()
- # Skip double insert due to extra selection event
- null
- else if e.thang and not (@addThangLank and @addThangType.get('name') in overlappableThangTypeNames)
+ previousSprite?.setNameLabel?(null) unless previousSprite is sprite
+
+ if thang and not (@addThangLank and @addThangType.get('name') in overlappableThangTypeNames)
# We clicked on a Thang (or its Treema), so select the Thang
- @selectAddThang null, true
+ @selectAddThang(null, true)
@selectedExtantThangClickTime = new Date()
# Show the label above selected thang, notice that we may get here from thang-edit-view, so it will be selected but no label
- @selectedExtantLank.setNameLabel @selectedExtantLank.thangType.get('name') + ': ' + @selectedExtantThang.id
- @selectedExtantLank.updateLabels()
- @selectedExtantLank.updateMarks()
- else if @addThangLank
- # We clicked on the background when we had an add Thang selected, so add it
- @addThang @addThangType, @addThangLank.thang.pos
- @lastAddTime = new Date()
+ sprite.setNameLabel(sprite.thangType.get('name') + ': ' + thang.id)
+ sprite.updateLabels()
+ sprite.updateMarks()
justAdded: -> @lastAddTime and (new Date() - @lastAddTime) < 150
@@ -482,11 +521,14 @@ module.exports = class ThangsTabView extends CocoView
deleteSelectedExtantThang: (e) =>
return if $(e.target).hasClass 'treema-node'
- return unless @selectedExtantThang
- thang = @getThangByID(@selectedExtantThang.id)
- @thangsTreema.delete(@pathForThang(thang))
- @deleteEmptyTreema(thang)
- Thang.resetThangIDs() # TODO: find some way to do this when we delete from treema, too
+ selected = @gameUIState.get('selected')
+ return unless _.any(selected)
+
+ for singleSelected in selected
+ thang = @getThangByID(singleSelected.thang.id)
+ @thangsTreema.delete(@pathForThang(thang))
+ @deleteEmptyTreema(thang)
+ Thang.resetThangIDs() # TODO: find some way to do this when we delete from treema, too
deleteEmptyTreema: (thang)->
thangType = @supermodel.getModelByOriginal ThangType, thang.thangType
@@ -564,17 +606,26 @@ module.exports = class ThangsTabView extends CocoView
@selectAddThangType @addThangType, @cloneSourceThang if @addThangType # make another addThang sprite, since the World just refreshed
# update selection, since the thangs have been remade
- if @selectedExtantThang
- @selectedExtantLank = @surface.lankBoss.lanks[@selectedExtantThang.id]
- @selectedExtantThang = @selectedExtantLank?.thang
+ selected = @gameUIState.get('selected')
+ if _.any(selected)
+ for singleSelected in selected
+ sprite = @surface.lankBoss.lanks[singleSelected.thang.id]
+ if sprite
+ sprite.updateMarks()
+ singleSelected.sprite = sprite
+ singleSelected.thang = sprite.thang
Backbone.Mediator.publish 'editor:thangs-edited', thangs: @world.thangs
onTreemaThangSelected: (e, selectedTreemas) =>
- selectedThangID = _.last(selectedTreemas)?.data.id
- if selectedThangID isnt @selectedExtantThang?.id
- @surface.lankBoss.selectThang selectedThangID, null, true
+ selectedThangTreemas = _.filter(selectedTreemas, (t) -> t instanceof ThangNode)
+ thangIDs = (node.data.id for node in selectedThangTreemas)
+ lanks = (@surface.lankBoss.lanks[thangID] for thangID in thangIDs when thangID)
+ selected = ({ thang: lank.thang, sprite: lank } for lank in lanks when lank)
+ @gameUIState.set('selected', selected)
onTreemaThangDoubleClicked: (e, treema) =>
+ nativeEvent = e.originalEvent.nativeEvent
+ return if nativeEvent and (nativeEvent.ctrlKey or nativeEvent.metaKey)
id = treema?.data?.id
@editThang thangID: id if id
@@ -655,7 +706,8 @@ module.exports = class ThangsTabView extends CocoView
onDuplicateClicked: (e) ->
$('#contextmenu').hide()
- @selectAddThangType @selectedExtantThang.spriteName, @selectedExtantThang
+ selected = _.last(@gameUIState.get('selected'))
+ @selectAddThangType(selected.thang.spriteName, selected.thang)
onClickRotationButton: (e) ->
$('#contextmenu').hide()
@@ -667,7 +719,8 @@ module.exports = class ThangsTabView extends CocoView
@hush = true
thangData = @getThangByID thang.id
thangData = $.extend true, {}, thangData
- unless component = _.find thangData.components, {original: componentOriginal}
+ component = _.find thangData.components, {original: componentOriginal}
+ unless component
component = original: componentOriginal, config: {}, majorVersion: 0
thangData.components.push component
modificationFunction component
@@ -682,34 +735,44 @@ module.exports = class ThangsTabView extends CocoView
lank.setDebug true
rotateSelectedThangTo: (radians) ->
- @modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
- component.config.rotation = radians
- @selectedExtantThang.rotation = component.config.rotation
+ for singleSelected in @gameUIState.get('selected')
+ selectedThang = singleSelected.thang
+ @modifySelectedThangComponentConfig selectedThang, LevelComponent.PhysicalID, (component) =>
+ component.config.rotation = radians
+ selectedThang.rotation = component.config.rotation
rotateSelectedThangBy: (radians) ->
- @modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
- component.config.rotation = ((component.config.rotation ? 0) + radians) % (2 * Math.PI)
- @selectedExtantThang.rotation = component.config.rotation
+ for singleSelected in @gameUIState.get('selected')
+ selectedThang = singleSelected.thang
+ @modifySelectedThangComponentConfig selectedThang, LevelComponent.PhysicalID, (component) =>
+ component.config.rotation = ((component.config.rotation ? 0) + radians) % (2 * Math.PI)
+ selectedThang.rotation = component.config.rotation
moveSelectedThangBy: (xDir, yDir) ->
- @modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
- component.config.pos.x += 0.5 * xDir
- component.config.pos.y += 0.5 * yDir
- @selectedExtantThang.pos.x = component.config.pos.x
- @selectedExtantThang.pos.y = component.config.pos.y
+ for singleSelected in @gameUIState.get('selected')
+ selectedThang = singleSelected.thang
+ @modifySelectedThangComponentConfig selectedThang, LevelComponent.PhysicalID, (component) =>
+ component.config.pos.x += 0.5 * xDir
+ component.config.pos.y += 0.5 * yDir
+ selectedThang.pos.x = component.config.pos.x
+ selectedThang.pos.y = component.config.pos.y
resizeSelectedThangBy: (xDir, yDir) ->
- @modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
- component.config.width = (component.config.width ? 4) + 0.5 * xDir
- component.config.height = (component.config.height ? 4) + 0.5 * yDir
- @selectedExtantThang.width = component.config.width
- @selectedExtantThang.height = component.config.height
+ for singleSelected in @gameUIState.get('selected')
+ selectedThang = singleSelected.thang
+ @modifySelectedThangComponentConfig selectedThang, LevelComponent.PhysicalID, (component) =>
+ component.config.width = (component.config.width ? 4) + 0.5 * xDir
+ component.config.height = (component.config.height ? 4) + 0.5 * yDir
+ selectedThang.width = component.config.width
+ selectedThang.height = component.config.height
toggleSelectedThangCollision: ->
- @modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.CollidesID, (component) =>
- component.config ?= {}
- component.config.collisionCategory = if component.config.collisionCategory is 'none' then 'ground' else 'none'
- @selectedExtantThang.collisionCategory = component.config.collisionCategory
+ for singleSelected in @gameUIState.get('selected')
+ selectedThang = singleSelected.thang
+ @modifySelectedThangComponentConfig selectedThang, LevelComponent.CollidesID, (component) =>
+ component.config ?= {}
+ component.config.collisionCategory = if component.config.collisionCategory is 'none' then 'ground' else 'none'
+ selectedThang.collisionCategory = component.config.collisionCategory
toggleThangsContainer: (e) ->
$('#all-thangs').toggleClass('hide')
diff --git a/app/views/editor/verifier/VerifierTest.coffee b/app/views/editor/verifier/VerifierTest.coffee
index 8ad49720a..d12dc6625 100644
--- a/app/views/editor/verifier/VerifierTest.coffee
+++ b/app/views/editor/verifier/VerifierTest.coffee
@@ -81,10 +81,10 @@ module.exports = class VerifierTest extends CocoClass
@updateCallback? state: 'running'
processSingleGameResults: (e) ->
- console.log(e)
@goals = e.goalStates
@frames = e.totalFrames
@lastFrameHash = e.lastFrameHash
+ @simulationFrameRate = e.simulationFrameRate
@state = 'complete'
@updateCallback? state: @state
@scheduleCleanup()
@@ -92,6 +92,7 @@ module.exports = class VerifierTest extends CocoClass
isSuccessful: () ->
return false unless @solution?
return false unless @frames == @solution.frameCount or @options.dontCareAboutFrames
+ return false if @simulationFrameRate < 30
if @goals and @solution.goals
for k of @goals
continue if not @solution.goals[k]
diff --git a/server/middleware/course-instances.coffee b/server/middleware/course-instances.coffee
index e64de3f48..362dc046c 100644
--- a/server/middleware/course-instances.coffee
+++ b/server/middleware/course-instances.coffee
@@ -72,6 +72,7 @@ module.exports =
fetchNextLevel: wrap (req, res) ->
+ unless req.user? then return res.status(200).send({})
levelOriginal = req.params.levelOriginal
unless database.isID(levelOriginal) then throw new errors.UnprocessableEntity('Invalid level original ObjectId')
sessionID = req.params.sessionID
@@ -162,3 +163,9 @@ module.exports =
students: (user.toObject({req: req}) for user in users)
prepaids: (prepaid.toObject({req: req}) for prepaid in prepaids)
})
+
+ fetchNonHoc: wrap (req, res) ->
+ throw new errors.Unauthorized('You must be an administrator.') unless req.user?.isAdmin()
+ query = {$and: [{name: {$ne: 'Single Player'}}, {hourOfCode: {$ne: true}}]}
+ courseInstances = yield CourseInstance.find(query, { members: 1, ownerID: 1}).lean()
+ res.status(200).send(courseInstances)
diff --git a/server/routes/index.coffee b/server/routes/index.coffee
index befd10c26..7217ee21a 100644
--- a/server/routes/index.coffee
+++ b/server/routes/index.coffee
@@ -80,6 +80,7 @@ module.exports.setup = (app) ->
app.get('/db/course/:handle', mw.rest.getByHandle(Course))
app.get('/db/course/:handle/levels/:levelOriginal/next', mw.courses.fetchNextLevel)
+ app.get('/db/course_instance/-/non-hoc', mw.auth.checkHasPermission(['admin']), mw.courseInstances.fetchNonHoc)
app.post('/db/course_instance/-/recent', mw.auth.checkHasPermission(['admin']), mw.courseInstances.fetchRecent)
app.get('/db/course_instance/:handle/levels/:levelOriginal/sessions/:sessionID/next', mw.courseInstances.fetchNextLevel)
app.post('/db/course_instance/:handle/members', mw.auth.checkLoggedIn(), mw.courseInstances.addMembers)
diff --git a/test/app/lib/surface/camera.spec.coffee b/test/app/lib/surface/Camera.spec.coffee
similarity index 92%
rename from test/app/lib/surface/camera.spec.coffee
rename to test/app/lib/surface/Camera.spec.coffee
index 9383ce89a..2ece688ac 100644
--- a/test/app/lib/surface/camera.spec.coffee
+++ b/test/app/lib/surface/Camera.spec.coffee
@@ -96,24 +96,6 @@ describe 'Camera (Surface point of view)', ->
checkConversionsFromWorldPos wop, cam
checkCameraPos cam, wop
- it 'works at 90 degrees', ->
- cam = new Camera {attr: (attr) -> 100}, Math.PI / 2
- expect(cam.x2y).toBeCloseTo 1
- expect(cam.x2z).toBeGreaterThan 9001
- expect(cam.z2y).toBeCloseTo 0
-
- it 'works at 0 degrees', ->
- cam = new Camera {attr: (attr) -> 100}, 0
- expect(cam.x2y).toBeGreaterThan 9001
- expect(cam.x2z).toBeCloseTo 1
- expect(cam.z2y).toBeGreaterThan 9001
-
- it 'works at 45 degrees', ->
- cam = new Camera {attr: (attr) -> 100}, Math.PI / 4
- expect(cam.x2y).toBeCloseTo Math.sqrt(2)
- expect(cam.x2z).toBeCloseTo Math.sqrt(2)
- expect(cam.z2y).toBeCloseTo 1
-
it 'works at default angle of asin(0.75) ~= 48.9 degrees', ->
cam = new Camera {attr: (attr) -> 100}, null
angle = Math.asin(3 / 4)
diff --git a/test/app/lib/surface/LankBoss.spec.coffee b/test/app/lib/surface/LankBoss.spec.coffee
index 10629c5d8..fb10d79bf 100644
--- a/test/app/lib/surface/LankBoss.spec.coffee
+++ b/test/app/lib/surface/LankBoss.spec.coffee
@@ -2,6 +2,7 @@ LankBoss = require 'lib/surface/LankBoss'
Camera = require 'lib/surface/Camera'
World = require 'lib/world/world'
ThangType = require 'models/ThangType'
+GameUIState = require 'models/GameUIState'
treeData = require 'test/app/fixtures/tree1.thang.type'
munchkinData = require 'test/app/fixtures/ogre-munchkin-m.thang.type'
@@ -53,6 +54,7 @@ describe 'LankBoss', ->
surfaceTextLayer: new createjs.Container()
world: world
thangTypes: thangTypes
+ gameUIState: new GameUIState()
}
window.lankBoss = lankBoss = new LankBoss(options)