This commit is contained in:
Scott Erickson 2014-03-11 22:01:47 -07:00
commit 9cb80783ff
21 changed files with 217 additions and 113 deletions

View file

@ -10,7 +10,7 @@ module.exports = class Simulator
@trigger 'statusUpdate', 'Starting simulation!'
@retryDelayInSeconds = 10
@taskURL = '/queue/scoring'
destroy: ->
@off()
@cleanupSimulation()
@ -25,17 +25,21 @@ module.exports = class Simulator
success: @setupSimulationAndLoadLevel
handleFetchTaskError: (errorData) =>
console.log "There were no games to score. Error: #{JSON.stringify errorData}"
console.log "Retrying in #{@retryDelayInSeconds}"
@trigger 'statusUpdate', 'There were no games to simulate! Trying again in 10 seconds.'
console.error "There was a horrible Error: #{JSON.stringify errorData}"
@trigger 'statusUpdate', 'There was an error fetching games to simulate. Retrying in 10 seconds.'
@simulateAnotherTaskAfterDelay()
handleNoGamesResponse: ->
@trigger 'statusUpdate', 'There were no games to simulate--nice. Retrying in 10 seconds.'
@simulateAnotherTaskAfterDelay()
simulateAnotherTaskAfterDelay: =>
console.log "Retrying in #{@retryDelayInSeconds}"
retryDelayInMilliseconds = @retryDelayInSeconds * 1000
_.delay @fetchAndSimulateTask, retryDelayInMilliseconds
setupSimulationAndLoadLevel: (taskData) =>
setupSimulationAndLoadLevel: (taskData, textStatus, jqXHR) =>
return @handleNoGamesResponse() if jqXHR.status is 204
@trigger 'statusUpdate', 'Setting up simulation!'
@task = new SimulationTask(taskData)
@supermodel = new SuperModel()

View file

@ -105,7 +105,6 @@ module.exports = class GoalManager extends CocoClass
notifyGoalChanges: ->
overallStatus = @checkOverallStatus()
event = {goalStates: @goalStates, goals: @goals, overallStatus: overallStatus}
#console.log JSON.stringify(event), "new goal states"
Backbone.Mediator.publish('goal-manager:new-goal-states', event)
checkOverallStatus: (ignoreIncomplete=false) ->
@ -126,6 +125,10 @@ module.exports = class GoalManager extends CocoClass
keyFrame: 0 # when it became a 'success' or 'failure'
}
@initGoalState(state, [goal.killThangs, goal.saveThangs], 'killed')
for getTo in goal.getAllToLocations ? []
@initGoalState(state,[ getTo.getToLocation?.who , [] ], 'arrived')
for keepFrom in goal.keepAllFromLocations ? []
@initGoalState(state,[ [] , keepFrom.keepFromLocation?.who], 'arrived')
@initGoalState(state, [goal.getToLocations?.who, goal.keepFromLocations?.who], 'arrived')
@initGoalState(state, [goal.leaveOffSides?.who, goal.keepFromLeavingOffSides?.who], 'left')
@initGoalState(state, [goal.collectThangs?.who, goal.keepFromCollectingThangs?.who], 'collected')
@ -143,7 +146,13 @@ module.exports = class GoalManager extends CocoClass
onThangTouchedGoal: (e, frameNumber) ->
for goal in @goals ? []
@checkArrived(goal.id, goal.getToLocations.who, goal.getToLocations.targets, e.actor, e.touched.id, frameNumber) if goal.getToLocations?
if goal.getAllToLocations?
for getTo in goal.getAllToLocations
@checkArrived(goal.id, getTo.getToLocation.who, getTo.getToLocation.targets, e.actor, e.touched.id, frameNumber)
@checkArrived(goal.id, goal.keepFromLocations.who, goal.keepFromLocations.targets, e.actor, e.touched.id, frameNumber) if goal.keepFromLocations?
if goal.keepAllFromLocations?
for keepFrom in goal.keepAllFromLocations
@checkArrived(goal.id, keepFrom.keepFromLocation.who , keepFrom.keepFromLocation.targets, e.actor, e.touched.id, frameNumber )
checkArrived: (goalID, who, targets, thang, touchedID, frameNumber) ->
return unless touchedID in targets
@ -191,6 +200,7 @@ module.exports = class GoalManager extends CocoClass
initGoalState: (state, whos, progressObjectName) ->
# 'whos' is an array of goal 'who' values.
# This inits the progress object for the goal tracking.
arrays = (prop for prop in whos when prop?.length)
return unless arrays.length
state[progressObjectName] = {}
@ -240,7 +250,9 @@ module.exports = class GoalManager extends CocoClass
killThangs: 1
saveThangs: 0
getToLocations: 1
getAllToLocations: 1
keepFromLocations: 0
keepAllFromLocations: 0
leaveOffSides: 1
keepFromLeavingOffSides: 0
collectThangs: 1

View file

@ -63,6 +63,7 @@ module.exports.thangNames = thangNames =
"Annie"
"Lukaz"
"Gorgin"
"Coco"
]
"Peasant": [
"Yorik"
@ -90,6 +91,7 @@ module.exports.thangNames = thangNames =
]
"Peasant F": [
"Hilda"
"Icey"
]
"Archer F": [
"Phoebe"
@ -115,6 +117,12 @@ module.exports.thangNames = thangNames =
"Alden"
"Cairn"
"Jensen"
"Yilitha"
"Mirana"
"Lina"
"Luna"
"Alleria"
"Vereesa"
]
"Archer M": [
"Brian"
@ -127,6 +135,14 @@ module.exports.thangNames = thangNames =
"Arty"
"Gimsley"
"Fidsdale"
"Slyvos"
"Logos"
"Denin"
"Lycan"
"Loco"
"Vican"
"Mars"
"Dev"
]
"Ogre Munchkin M": [
"Brack"
@ -148,6 +164,7 @@ module.exports.thangNames = thangNames =
"Thabt"
"Snortt"
"Kog"
"Ursa"
]
"Ogre Munchkin F": [
"Iyert"
@ -155,6 +172,9 @@ module.exports.thangNames = thangNames =
"Shmeal"
"Gurzunn"
"Yugark"
"Dosha"
"Inski"
"Lacos"
]
"Ogre M": [
"Krogg"
@ -168,6 +188,9 @@ module.exports.thangNames = thangNames =
"Vargutt"
"Grumus"
"Gug"
"Tarlok"
"Gurulax"
"Mokrul"
]
"Ogre F": [
"Nareng"
@ -175,6 +198,11 @@ module.exports.thangNames = thangNames =
"Glonc"
"Marghurk"
"Martha"
"Holkam"
"Alkaz"
"Gar'ah"
"Mak'rah"
"Marnag"
]
"Ogre Brawler": [
"Grul'thock"
@ -190,6 +218,8 @@ module.exports.thangNames = thangNames =
"Grognar"
"Ironjaw"
"Tuguro"
"York"
"Ork'han"
]
"Ogre Fangrider": [
"Dreek"
@ -205,6 +235,7 @@ module.exports.thangNames = thangNames =
"Gurzthrot"
"Murgark"
"Muttin"
"Bortrok"
]
"Ogre Shaman": [
"Sham'uk"
@ -224,6 +255,11 @@ module.exports.thangNames = thangNames =
"Zo'Goroth"
"Mogadishu"
"Nazgareth"
"Gror"
"Grek"
"Gom"
"Gogg"
"Ghuk"
]
"Ogre Thrower": [
"Kyrgg"

View file

@ -345,40 +345,40 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman
rights_clarification: "Pentru a clarifica, orice este valabil in Editorul de Nivele pentru scopul de a crea nivele se află sub CC,pe când conținutul creat cu Editorul de Nivele sau încărcat pentru a face nivelul nu se află." #CC stands for...?
nutshell_title: "Pe scurt"
nutshell_description: "Orice resurse vă punem la dispoziție în Editorul de Nivele puteți folosi liber cum vreți pentru a crea nivele. Dar ne rezervăm dreptul de a rezerva distribuția de nivele în sine (care sunt create pe codecombat.com) astfel încât să se poată percepe o taxă pentru ele pe vitor, dacă se va ajunge la așa ceva."
canonical: "The English version of this document is the definitive, canonical version. If there are any discrepencies between translations, the English document takes precedence."
canonical: "Versiunea in engleză a acestui document este cea definitivă, versiunea canonică. Dacă există orice discrepanțe între traduceri, documentul in engleză are prioritate."
# contribute:
# page_title: "Contributing"
# character_classes_title: "Character Classes"
# introduction_desc_intro: "We have high hopes for CodeCombat."
# introduction_desc_pref: "We want to be where programmers of all stripes come to learn and play together, introduce others to the wonderful world of coding, and reflect the best parts of the community. We can't and don't want to do that alone; what makes projects like GitHub, Stack Overflow and Linux great are the people who use them and build on them. To that end, "
# introduction_desc_github_url: "CodeCombat is totally open source"
# introduction_desc_suf: ", and we aim to provide as many ways as possible for you to take part and make this project as much yours as ours."
# introduction_desc_ending: "We hope you'll join our party!"
# introduction_desc_signature: "- Nick, George, Scott, Michael, and Jeremy"
# alert_account_message_intro: "Hey there!"
# alert_account_message_pref: "To subscribe for class emails, you'll need to "
# alert_account_message_suf: "first."
# alert_account_message_create_url: "create an account"
# archmage_summary: "Interested in working on game graphics, user interface design, database and server organization, multiplayer networking, physics, sound, or game engine performance? Want to help build a game to help other people learn what you are good at? We have a lot to do and if you are an experienced programmer and want to develop for CodeCombat, this class is for you. We would love your help building the best programming game ever."
# archmage_introduction: "One of the best parts about building games is they synthesize so many different things. Graphics, sound, real-time networking, social networking, and of course many of the more common aspects of programming, from low-level database management, and server administration to user facing design and interface building. There's a lot to do, and if you're an experienced programmer with a hankering to really dive into the nitty-gritty of CodeCombat, this class might be for you. We would love to have your help building the best programming game ever."
# class_attributes: "Class Attributes"
# archmage_attribute_1_pref: "Knowledge in "
# archmage_attribute_1_suf: ", or a desire to learn. Most of our code is in this language. If you're a fan of Ruby or Python, you'll feel right at home. It's JavaScript, but with a nicer syntax."
# archmage_attribute_2: "Some experience in programming and personal initiative. We'll help you get oriented, but we can't spend much time training you."
# how_to_join: "How To Join"
# join_desc_1: "Anyone can help out! Just check out our "
# join_desc_2: "to get started, and check the box below to mark yourself as a brave Archmage and get the latest news by email. Want to chat about what to do or how to get more deeply involved? "
# join_desc_3: ", or find us in our "
# join_desc_4: "and we'll go from there!"
# join_url_email: "Email us"
# join_url_hipchat: "public HipChat room"
# more_about_archmage: "Learn More About Becoming an Archmage"
# archmage_subscribe_desc: "Get emails on new coding opportunities and announcements."
# artisan_summary_pref: "Want to design levels and expand CodeCombat's arsenal? People are playing through our content at a pace faster than we can build! Right now, our level editor is barebone, so be wary. Making levels will be a little challenging and buggy. If you have visions of campaigns spanning for-loops to"
# artisan_summary_suf: "then this class is for you."
# artisan_introduction_pref: "We must construct additional levels! People be clamoring for more content, and we can only build so many ourselves. Right now your workstation is level one; our level editor is barely usable even by its creators, so be wary. If you have visions of campaigns spanning for-loops to"
# artisan_introduction_suf: "then this class might be for you."
contribute:
page_title: "Contribuțtii"
character_classes_title: "Clase de caractere"
introduction_desc_intro: "Avem speranțe mari pentru CodeCombat."
introduction_desc_pref: "Vrem să fie locul unde programatori de toate rangurile vin să învețe și să se distreze împreună, introduc pe alții in minunata lume a programării, și reflectă cele mai bune părți ale comunității. Nu vrem și nu putem să facem asta singuri; ceea ce face proiectele precum GitHub, Stack Overflow și Linux geniale sunt oameni care le folosesc și construiec peste ele. Cu scopul acesta, "
introduction_desc_github_url: "CodeCombat este complet open source"
introduction_desc_suf: ", și ne propunem să vă punem la dispoziție pe cât de mult posibil modalități de a lua parte la acest proiect pentru a-l face la fel de mult as vostru cât și al nostru."
introduction_desc_ending: "Sperăm să vă placă petrecerea noastră!"
introduction_desc_signature: "- Nick, George, Scott, Michael, și Jeremy"
alert_account_message_intro: "Salutare!"
alert_account_message_pref: "Pentru a te abona la email-uri de clasă, va trebui să "
alert_account_message_suf: "mai întâi."
alert_account_message_create_url: "creați un cont"
archmage_summary: "Interesat să lucrezi la grafica jocului, interfața grafică cu utilizatorul, baze de date și organizare server , multiplayer networking, fizică, sunet, sau performanțe game engine ? Vrei să ajuți la construirea unui joc pentru a învăța pe alții ceea ce te pricepi? Avem o grămadă de făcut dacă ești un programator experimentat și vrei sa dezvolți pentru CodeCombat, această clasă este pentru tine. Ne-ar plăcea să ne ajuți să construim cel mai bun joc de programare făcut vreodată."
archmage_introduction: "Una dintre cele mai bune părți despre construirea unui joc este că sintetizează atât de multe lucruri diferite. Grafică, sunet, networking în timp real, social networking, și desigur multe dintre aspectele comune ale programării, de la gestiune low-level a bazelor de date , și administrare server până la construirea de interfețe. Este mult de muncă, și dacă ești un programator cu experiență, cu un dor de a se arunca cu capul înainte îm CodeCombat, această clasă ți se potrivește. Ne-ar plăcea să ne ajuți să construim cel mai bun joc de programare făcut vreodată."
class_attributes: "Atribute pe clase"
archmage_attribute_1_pref: "Cunoștințe în "
archmage_attribute_1_suf: ", sau o dorință de a învăța. Majoritatea codului este în acest limbaj. Dacă ești fan Ruby sau Python, te vei simți ca acasă. Este JavaScript, dar cu o sintaxă mai frumoasă."
archmage_attribute_2: "Ceva experiență în programare și inițiativă personală. Te vom ajuta să te orientezi, dar nu putem aloca prea mult timp pentru a te pregăti."
how_to_join: "Cum să ni te alături"
join_desc_1: "Oricine poate să ajute! Doar intrați pe "
join_desc_2: "pentru a începe, și bifați căsuța de dedesubt pentru a te marca ca un Archmage curajos și pentru a primi ultimele știri pe email. Vrei să discuți despre ce să faci sau cum să te implici mai mult? "
join_desc_3: ", sau găsește-ne în "
join_desc_4: "și pornim de acolo!"
join_url_email: "Trimite-ne Email"
join_url_hipchat: "public HipChat room"
more_about_archmage: "Învață mai multe despre cum să devi un Archmage"
archmage_subscribe_desc: "Primește email-uri despre noi oportunități de progrmare și anunțuri."
artisan_summary_pref: "Vrei să creezi nivele și să extinzi arsenalul CodeCombat? Oamenii ne termină nivelele mai repede decât putem să le creăm! Momentan, editorul nostru de nivele este rudimentar, așa că aveți grijă. Crearea de nivele va fi o mică provocare și va mai avea câteva bug-uri. Dacă ai viziuni cu campanii care cuprind loop-uri for pentru"
artisan_summary_suf: "atunci asta e clasa pentru tine."
artisan_introduction_pref: "Trebuie să construim nivele adiționale! Oamenii sunt nerăbdători pentru mai mult conținut, și noi putem face doar atât singuri. Momentan editorul de nivele abia este utilizabil până și de creatorii lui, așa că aveți grijă. Dacă ai viziuni cu campanii care cuprind loop-uri for pentru"
artisan_introduction_suf: "atunci aceasta ar fi clasa pentru tine."
# artisan_attribute_1: "Any experience in building content like this would be nice, such as using Blizzard's level editors. But not required!"
# artisan_attribute_2: "A hankering to do a whole lot of testing and iteration. To make good levels, you need to take it to others and watch them play it, and be prepared to find a lot of things to fix."
# artisan_attribute_3: "For the time being, endurance en par with an Adventurer. Our Level Editor is super preliminary and frustrating to use. You have been warned!"

View file

@ -152,7 +152,7 @@ class CocoModel extends Backbone.Model
return null unless schema.links?
linkObject = _.find schema.links, rel: "db"
return null unless linkObject
return null if linkObject.href.match("thang_type") and not @isObjectID(data) # Skip loading hardcoded Thang Types for now (TODO)
return null if linkObject.href.match("thang.type") and not @isObjectID(data) # Skip loading hardcoded Thang Types for now (TODO)
# not fully extensible, but we can worry about that later
link = linkObject.href

View file

@ -112,7 +112,7 @@ module.exports = class Level extends CocoModel
if path.match(/\/systems\/\d+\/config\//) and data?.indieSprites?.length
# Ugh, we need to make sure we grab the IndieSprite ThangTypes
for indieSprite in data.indieSprites
link = "/db/thang_type/#{indieSprite.thangType}/version"
link = "/db/thang.type/#{indieSprite.thangType}/version"
model = CocoModel.getOrMakeModelFromLink link, shouldLoadProjection
models.push model if model
else if path is '/'

View file

@ -218,8 +218,8 @@ module.exports = class ThangType extends CocoModel
@loadUniversalWizard: ->
return @wizardType if @wizardType
wizOriginal = "52a00d55cf1818f2be00000b"
url = "/db/thang_type/#{wizOriginal}/version"
url = "/db/thang.type/#{wizOriginal}/version"
@wizardType = new module.exports()
@wizardType.url = -> url
@wizardType.fetch()
@wizardType
@wizardType

View file

@ -18,5 +18,13 @@
white-space: nowrap
overflow: hidden
tr.stale
opacity: 0.5
tr.win .state-cell
color: #172
tr.loss .state-cell
color: #712
#must-log-in button
margin-right: 10px
margin-right: 10px

View file

@ -17,9 +17,10 @@ div#columns.row
button.btn.btn-sm.btn-warning.pull-right.rank-button(data-session-id=team.session.id)
span.unavailable.hidden No New Code to Rank
span.rank.hidden Rank My Game!
span.ranking.hidden Submitting...
span.ranked.hidden Submitted for Ranking
span.submitting.hidden Submitting...
span.submitted.hidden Submitted for Ranking
span.failed.hidden Failed to Rank
span.ranking.hidden Game Being Ranked
if team.chartData
tr
@ -32,7 +33,7 @@ div#columns.row
th When
th
for match in team.matches
tr
tr(class=(match.stale ? "stale " : "") + match.state)
td.state-cell
if match.state === 'win'
span.win Win
@ -48,7 +49,12 @@ div#columns.row
if !team.matches.length
tr
td(colspan=4).alert.alert-warning
| No ranked matches for this team!
| Play against some competitors and then come back here to get your game ranked.
if team.isRanking
td(colspan=4).alert.alert-info
| Your new code is being simulated by other players for ranking.
| This will refresh as new matches come in.
else
td(colspan=4).alert.alert-warning
| No ranked matches for the #{team.name} team!
| Play against some competitors and then come back here to get your game ranked.

View file

@ -22,7 +22,7 @@ module.exports = class WizardSettingsView extends CocoView
loadWizard: ->
@wizardThangType = new ThangType()
@wizardThangType.url = -> '/db/thang_type/wizard'
@wizardThangType.url = -> '/db/thang.type/wizard'
@wizardThangType.fetch()
@wizardThangType.once 'sync', @initCanvas, @
@ -67,7 +67,7 @@ module.exports = class WizardSettingsView extends CocoView
updateSwatchVisibility: (colorGroup) ->
enabled = colorGroup.find('.color-group-checkbox').prop('checked')
colorGroup.find('.minicolors-swatch').toggle Boolean(enabled)
updateColorSettings: (colorGroup) =>
wizardSettings = _.cloneDeep(me.get('wizard')) or {}
wizardSettings.colorConfig ?= {}
@ -108,4 +108,4 @@ module.exports = class WizardSettingsView extends CocoView
@movieClip.regX = reg.x
@movieClip.regY = reg.y
@stage.addChild @movieClip
@stage.update()
@stage.update()

View file

@ -19,7 +19,7 @@ componentOriginals =
"physics.Physical" : "524b75ad7fc0f6d519000001"
class ThangTypeSearchCollection extends CocoCollection
url: '/db/thang_type/search?project=true'
url: '/db/thang.type/search?project=true'
model: ThangType
module.exports = class ThangsTabView extends View

View file

@ -54,7 +54,7 @@ module.exports = class MyMatchesTabView extends CocoView
ctx.levelID = @level.get('slug') or @level.id
ctx.teams = @teams
convertMatch = (match) =>
convertMatch = (match, submitDate) =>
opponent = match.opponents[0]
state = 'win'
state = 'loss' if match.metrics.rank > opponent.metrics.rank
@ -65,12 +65,14 @@ module.exports = class MyMatchesTabView extends CocoView
opponentID: opponent.userID
when: moment(match.date).fromNow()
sessionID: opponent.sessionID
stale: match.date < submitDate
}
for team in @teams
team.session = (s for s in @sessions.models when s.get('team') is team.id)[0]
team.readyToRank = @readyToRank(team.session)
team.matches = (convertMatch(match) for match in team.session?.get('matches') or [])
team.isRanking = team.session?.get('isRanking')
team.matches = (convertMatch(match, team.session.get('submitDate')) for match in team.session?.get('matches') or [])
team.matches.reverse()
team.score = (team.session?.get('totalScore') or 10).toFixed(2)
team.wins = _.filter(team.matches, {state: 'win'}).length
@ -96,7 +98,12 @@ module.exports = class MyMatchesTabView extends CocoView
button = $(el)
sessionID = button.data('session-id')
session = _.find @sessions.models, { id: sessionID }
@setRankingButtonText button, if @readyToRank(session) then 'rank' else 'unavailable'
rankingState = 'unavailable'
if @readyToRank session
rankingState = 'rank'
else if session.get 'isRanking'
rankingState = 'ranking'
@setRankingButtonText button, rankingState
readyToRank: (session) ->
return false unless session?.get('levelID') # If it hasn't been denormalized, then it's not ready.
@ -110,11 +117,12 @@ module.exports = class MyMatchesTabView extends CocoView
session = _.find @sessions.models, { id: sessionID }
return unless @readyToRank(session)
@setRankingButtonText(button, 'ranking')
success = => @setRankingButtonText(button, 'ranked')
@setRankingButtonText(button, 'submitting')
success = => @setRankingButtonText(button, 'submitted')
failure = => @setRankingButtonText(button, 'failed')
ajaxData = { session: sessionID, levelID: @level.id, originalLevelID: @level.attributes.original, levelMajorVersion: @level.attributes.version.major }
console.log "Posting game for ranking from My Matches view."
$.ajax '/queue/scoring', {
type: 'POST'
data: ajaxData

View file

@ -66,6 +66,7 @@ module.exports = class VictoryModal extends View
ajaxData = session: @session.id, levelID: @level.id, originalLevelID: @level.get('original'), levelMajorVersion: @level.get('version').major
ladderURL = "/play/ladder/#{@level.get('slug')}#my-matches"
goToLadder = -> Backbone.Mediator.publish 'router:navigate', route: ladderURL
console.log "Posting game for ranking from victory modal."
$.ajax '/queue/scoring',
type: 'POST'
data: ajaxData

View file

@ -430,6 +430,7 @@ module.exports = class SpellView extends View
flow ?= @spellThang?.castAether?.flow
return unless flow
executed = []
executedRows = {}
matched = false
states = flow.states ? []
currentCallIndex = null
@ -445,20 +446,24 @@ module.exports = class SpellView extends View
matched = true
break
_.last(executed).push state
executedRows[state.range[0].row] = true
#state.executing = true if state.userInfo?.time is @thang.world.age # no work
currentCallIndex ?= callNumber - 1
#console.log "got call index", currentCallIndex, "for time", @thang.world.age, "out of", states.length
@decoratedGutter = @decoratedGutter || {}
# TODO: don't redo the markers if they haven't actually changed
for markerRange in (@markerRanges ?= [])
markerRange.start.detach()
markerRange.end.detach()
@aceSession.removeMarker markerRange.id
@markerRanges = []
@aceSession.removeGutterDecoration row, 'executing' for row in [0 ... @aceSession.getLength()]
@aceSession.removeGutterDecoration row, 'executed' for row in [0 ... @aceSession.getLength()]
$(@ace.container).find('.ace_gutter-cell.executing').removeClass('executing')
$(@ace.container).find('.ace_gutter-cell.executed').removeClass('executed')
for row in [0 ... @aceSession.getLength()]
unless executedRows[row]
@aceSession.removeGutterDecoration row, 'executing'
@aceSession.removeGutterDecoration row, 'executed'
@decoratedGutter[row] = ''
if not executed.length or (@spell.name is "plan" and @spellThang.castAether.metrics.statementsExecuted < 20)
@toolbarView?.toggleFlow false
@debugView.setVariableStates {}
@ -486,7 +491,10 @@ module.exports = class SpellView extends View
markerRange.end = @aceDoc.createAnchor markerRange.end
markerRange.id = @aceSession.addMarker markerRange, clazz, markerType
@markerRanges.push markerRange
@aceSession.addGutterDecoration start.row, clazz
if executedRows[start.row] and @decoratedGutter[start.row] isnt clazz
@aceSession.removeGutterDecoration start.row, @decoratedGutter[start.row] if @decoratedGutter[start.row] isnt ''
@aceSession.addGutterDecoration start.row, clazz
@decoratedGutter[start.row] = clazz
@debugView.setVariableStates {} unless gotVariableStates
null

View file

@ -36,9 +36,19 @@ GoalSchema = c.object {title: "Goal", description: "A goal that the player can a
getToLocations: c.object {title: "Get To Locations", description: "Will be set off when any of the \"who\" touch any of the \"targets\" ", required: ["who", "targets"]},
who: c.array {title: "Who", description: "The Thangs who must get to the target locations.", minItems: 1}, thang
targets: c.array {title: "Targets", description: "The target locations to which the Thangs must get.", minItems: 1}, thang
getAllToLocations: c.array {title: "Get all to locations", description: "Similar to getToLocations but now a specific \"who\" can have a specific \"target\", also must be used with the HowMany property for desired effect",required: ["getToLocation"]},
c.object {title: "", description: ""},
getToLocation: c.object {title: "Get To Locations", description: "TODO: explain", required: ["who", "targets"]},
who: c.array {title: "Who", description: "The Thangs who must get to the target locations.", minItems: 1}, thang
targets: c.array {title: "Targets", description: "The target locations to which the Thangs must get.", minItems: 1}, thang
keepFromLocations: c.object {title: "Keep From Locations", description: "TODO: explain", required: ["who", "targets"]},
who: c.array {title: "Who", description: "The Thangs who must not get to the target locations.", minItems: 1}, thang
targets: c.array {title: "Targets", description: "The target locations to which the Thangs must not get.", minItems: 1}, thang
keepAllFromLocations: c.array {title: "Keep ALL From Locations", description: "Similar to keepFromLocations but now a specific \"who\" can have a specific \"target\", also must be used with the HowMany property for desired effect", required: ["keepFromLocation"]},
c.object {title: "", description: ""},
keepFromLocation: c.object {title: "Keep From Locations", description: "TODO: explain", required: ["who", "targets"]},
who: c.array {title: "Who", description: "The Thangs who must not get to the target locations.", minItems: 1}, thang
targets: c.array {title: "Targets", description: "The target locations to which the Thangs must not get.", minItems: 1}, thang
leaveOffSides: c.object {title: "Leave Off Sides", description: "Sides of the level to get some Thangs to leave across.", required: ["who", "sides"]},
who: c.array {title: "Who", description: "The Thangs which must leave off the sides of the level.", minItems: 1}, thang
sides: c.array {title: "Sides", description: "The sides off which the Thangs must leave.", minItems: 1}, side
@ -164,7 +174,7 @@ LevelThangSchema = c.object {
},
id: thang # TODO: figure out if we can make this unique and how to set dynamic defaults
# TODO: split thangType into "original" and "majorVersion" like the rest for consistency
thangType: c.objectId(links: [{rel: "db", href: "/db/thang_type/{($)}/version"}], title: "Thang Type", description: "A reference to the original Thang template being configured.", format: 'thang-type')
thangType: c.objectId(links: [{rel: "db", href: "/db/thang.type/{($)}/version"}], title: "Thang Type", description: "A reference to the original Thang template being configured.", format: 'thang-type')
components: c.array {title: "Components", description: "Thangs are configured by changing the Components attached to them.", uniqueItems: true, format: 'thang-components-array'}, ThangComponentSchema # TODO: uniqueness should be based on "original", not whole thing
LevelSystemSchema = c.object {

View file

@ -140,6 +140,10 @@ _.extend LevelSessionSchema.properties,
submittedCode:
type: 'object'
isRanking:
type: 'boolean'
description: 'Whether this session is still in the first ranking chain after being submitted.'
unsubscribed:
type: 'boolean'
description: 'Whether the player has opted out of receiving email updates about ladder rankings for this session.'
@ -147,6 +151,7 @@ _.extend LevelSessionSchema.properties,
numberOfWinsAndTies:
type: 'number'
default: 0
numberOfLosses:
type: 'number'
default: 0
@ -162,7 +167,6 @@ _.extend LevelSessionSchema.properties,
items:
type: 'number'
matches:
type: 'array'
title: 'Matches'

View file

@ -61,4 +61,4 @@ LevelThangTypeSchema.plugin(plugins.PermissionsPlugin)
LevelThangTypeSchema.plugin(plugins.NamedPlugin)
LevelThangTypeSchema.plugin(plugins.SearchablePlugin, {searchable: ['name', 'description']})
module.exports = LevelThangType = mongoose.model('level.thang_type', LevelThangTypeSchema)
module.exports = LevelThangType = mongoose.model('level.thang.type', LevelThangTypeSchema)

View file

@ -52,7 +52,7 @@ module.exports.createNewTask = (req, res) ->
requestLevelID = req.body.originalLevelID
requestCurrentLevelID = req.body.levelID
requestLevelMajorVersion = parseInt(req.body.levelMajorVersion)
validatePermissions req, requestSessionID, (error, permissionsAreValid) ->
if err? then return errors.serverError res, "There was an error validating permissions"
unless permissionsAreValid then return errors.forbidden res, "You do not have the permissions to submit that game to the leaderboard"
@ -60,31 +60,33 @@ module.exports.createNewTask = (req, res) ->
return errors.badInput res, "The session ID is invalid" unless typeof requestSessionID is "string"
Level.findOne({_id: requestCurrentLevelID}).lean().select('type').exec (err, levelWithType) ->
if err? then return errors.serverError res, "There was an error finding the level type"
if not levelWithType.type or levelWithType.type isnt "ladder"
if not levelWithType.type or levelWithType.type isnt "ladder"
console.log "The level type of level with ID #{requestLevelID} is #{levelWithType.type}"
return errors.badInput res, "That level isn't a ladder level"
fetchSessionToSubmit requestSessionID, (err, sessionToSubmit) ->
if err? then return errors.serverError res, "There was an error finding the given session."
updateSessionToSubmit sessionToSubmit, (err, data) ->
if err? then return errors.serverError res, "There was an error updating the session"
opposingTeam = calculateOpposingTeam(sessionToSubmit.team)
fetchInitialSessionsToRankAgainst opposingTeam,requestLevelID, requestLevelMajorVersion, (err, sessionsToRankAgainst) ->
if err? then return errors.serverError res, "There was an error fetching the sessions to rank against"
taskPairs = generateTaskPairs(sessionsToRankAgainst, sessionToSubmit)
sendEachTaskPairToTheQueue taskPairs, (taskPairError) ->
if taskPairError? then return errors.serverError res, "There was an error sending the task pairs to the queue"
sendResponseObject req, res, {"message":"All task pairs were succesfully sent to the queue"}
module.exports.dispatchTaskToConsumer = (req, res) ->
if isUserAnonymous(req) then return errors.forbidden res, "You need to be logged in to simulate games"
scoringTaskQueue.receiveMessage (err, message) ->
if err? or messageIsInvalid(message) then return errors.gatewayTimeoutError res, "Queue Receive Error:#{err}"
if err? or messageIsInvalid(message)
res.send 204, "No games to score. #{message}"
return res.end()
console.log "Received Message"
messageBody = parseTaskQueueMessage req, res, message
return unless messageBody?
@ -118,51 +120,56 @@ module.exports.processTaskResult = (req, res) ->
scoringTaskQueue.deleteMessage clientResponseObject.receiptHandle, (err) ->
console.log "Deleted message."
if err? then return errors.badInput res, "The queue message is already back in the queue, rejecting results."
LevelSession.findOne(_id: clientResponseObject.originalSessionID).lean().exec (err, levelSession) ->
if err? then return errors.serverError res, "There was a problem finding the level session:#{err}"
supposedSubmissionDate = new Date(clientResponseObject.sessions[0].submitDate)
if Number(supposedSubmissionDate) isnt Number(levelSession.submitDate)
return sendResponseObject req, res, {"message":"The game has been resubmitted. Removing from queue..."}
logTaskComputation clientResponseObject, taskLog, (logErr) ->
if logErr? then return errors.serverError res, "There as a problem logging the task computation: #{logErr}"
updateSessions clientResponseObject, (updateError, newScoreArray) ->
if updateError? then return errors.serverError res, "There was an error updating the scores.#{updateError}"
newScoresObject = _.indexBy newScoreArray, 'id'
addMatchToSessions clientResponseObject, newScoresObject, (err, data) ->
if err? then return errors.serverError res, "There was an error updating the sessions with the match! #{JSON.stringify err}"
originalSessionID = clientResponseObject.originalSessionID
originalSessionTeam = clientResponseObject.originalSessionTeam
originalSessionRank = parseInt clientResponseObject.originalSessionRank
determineIfSessionShouldContinueAndUpdateLog originalSessionID, originalSessionRank, (err, sessionShouldContinue) ->
if err? then return errors.serverError res, "There was an error determining if the session should continue, #{err}"
if sessionShouldContinue
opposingTeam = calculateOpposingTeam(originalSessionTeam)
opponentID = _.pull(_.keys(newScoresObject), originalSessionID)
sessionNewScore = newScoresObject[originalSessionID].totalScore
opponentNewScore = newScoresObject[opponentID].totalScore
levelOriginalID = levelSession.level.original
levelOriginalMajorVersion = levelSession.level.majorVersion
findNearestBetterSessionID levelOriginalID, levelOriginalMajorVersion, originalSessionID, sessionNewScore, opponentNewScore, opponentID ,opposingTeam, (err, opponentSessionID) ->
findNearestBetterSessionID levelOriginalID, levelOriginalMajorVersion, originalSessionID, sessionNewScore, opponentNewScore, opponentID, opposingTeam, (err, opponentSessionID) ->
if err? then return errors.serverError res, "There was an error finding the nearest sessionID!"
unless opponentSessionID then return sendResponseObject req, res, {"message":"There were no more games to rank(game is at top!"}
addPairwiseTaskToQueue [originalSessionID, opponentSessionID], (err, success) ->
if err? then return errors.serverError res, "There was an error sending the pairwise tasks to the queue!"
sendResponseObject req, res, {"message":"The scores were updated successfully and more games were sent to the queue!"}
if opponentSessionID
addPairwiseTaskToQueue [originalSessionID, opponentSessionID], (err, success) ->
if err? then return errors.serverError res, "There was an error sending the pairwise tasks to the queue!"
sendResponseObject req, res, {"message":"The scores were updated successfully and more games were sent to the queue!"}
else
LevelSession.update {_id: originalSessionID}, {isRanking: false}, {multi: false}, (err, affected) ->
if err? then return errors.serverError res, "There was an error marking the victorious session as not being ranked."
return sendResponseObject req, res, {"message":"There were no more games to rank (game is at top)!"}
else
console.log "Player lost, achieved rank #{originalSessionRank}"
sendResponseObject req, res, {"message":"The scores were updated successfully, person lost so no more games are being inserted!"}
LevelSession.update {_id: originalSessionID}, {isRanking: false}, {multi: false}, (err, affected) ->
if err? then return errors.serverError res, "There was an error marking the completed session as not being ranked."
sendResponseObject req, res, {"message":"The scores were updated successfully, person lost so no more games are being inserted!"}
determineIfSessionShouldContinueAndUpdateLog = (sessionID, sessionRank, cb) ->
@ -234,11 +241,11 @@ findNearestBetterSessionID = (levelOriginalID, levelMajorVersion, sessionID, ses
retrieveAllOpponentSessionIDs = (sessionID, cb) ->
query = LevelSession.findOne({"_id":sessionID})
.select('matches.opponents.sessionID')
.select('matches.opponents.sessionID matches.date submitDate')
.lean()
query.exec (err, session) ->
if err? then return cb err, null
opponentSessionIDs = (match.opponents[0].sessionID for match in session.matches)
opponentSessionIDs = (match.opponents[0].sessionID for match in session.matches when match.date > session.submitDate)
cb err, opponentSessionIDs
@ -285,7 +292,7 @@ updateMatchesInSession = (matchObject, sessionID, callback) ->
currentMatchObject.opponents = opponentsArray
sessionUpdateObject =
$push: {matches: currentMatchObject}
$push: {matches: {$each: [currentMatchObject], $slice: -200}}
log.info "Updating session #{sessionID}"
LevelSession.update {"_id":sessionID}, sessionUpdateObject, callback
@ -304,12 +311,12 @@ updateSessionToSubmit = (sessionToUpdate, callback) ->
submitted: true
submittedCode: sessionToUpdate.code
submitDate: new Date()
matches: []
meanStrength: 25
standardDeviation: 25/3
totalScore: 10
numberOfWinsAndTies: 0
numberOfLosses: 0
isRanking: true
LevelSession.update {_id: sessionToUpdate._id}, sessionUpdateObject, callback
fetchInitialSessionsToRankAgainst = (opposingTeam, levelID, levelMajorVersion, callback) ->
@ -321,7 +328,7 @@ fetchInitialSessionsToRankAgainst = (opposingTeam, levelID, levelMajorVersion, c
submittedCode:
$exists: true
team: opposingTeam
sortParameters =
totalScore: 1
@ -449,8 +456,7 @@ updateScoreInSession = (scoreObject,callback) ->
meanStrength: scoreObject.meanStrength
standardDeviation: scoreObject.standardDeviation
totalScore: newTotalScore
$push:
scoreHistory: scoreHistoryAddition
$push: {scoreHistory: {$each: [scoreHistoryAddition], $slice: -1000}}
LevelSession.update {"_id": scoreObject.id}, updateObject, callback
log.info "New total score for session #{scoreObject.id} is #{updateObject.totalScore}"

View file

@ -102,7 +102,7 @@ sendLadderUpdateEmail = (session, daysAgo) ->
score_history_graph_url: getScoreHistoryGraphURL session, daysAgo
defeat: defeatContext
victory: victoryContext
log.info "Sending ladder update email to #{context.recipient.address} with #{context.email_data.wins} wins and #{context.email_data.losses} since #{daysAgo} day(s) ago."
log.info "Sending ladder update email to #{context.recipient.address} with #{context.email_data.wins} wins and #{context.email_data.losses} losses since #{daysAgo} day(s) ago."
sendwithus.api.send context, (err, result) ->
log.error "Error sending ladder update email: #{err} with result #{result}" if err

View file

@ -7,8 +7,8 @@ module.exports.setupRoutes = (app) ->
return
options = { DEBUG: not config.isProduction }
module.exports.api = new sendwithusAPI swuAPIKey, options
debug = not config.isProduction
module.exports.api = new sendwithusAPI swuAPIKey, debug
module.exports.templates =
welcome_email: 'utnGaBHuSU4Hmsi7qrAypU'
ladder_update_email: 'JzaZxf39A4cKMxpPZUfWy4'

View file

@ -3,6 +3,7 @@ jsonschema = require('./user_schema')
crypto = require('crypto')
{salt, isProduction} = require('../../server_config')
mail = require '../commons/mail'
log = require 'winston'
sendwithus = require '../sendwithus'
@ -27,7 +28,7 @@ UserSchema.post('init', ->
UserSchema.methods.isAdmin = ->
p = @get('permissions')
return p and 'admin' in p
UserSchema.statics.updateMailChimp = (doc, callback) ->
return callback?() unless isProduction
return callback?() if doc.updatedMailChimp
@ -41,25 +42,25 @@ UserSchema.statics.updateMailChimp = (doc, callback) ->
return callback?() # don't add totally unsubscribed people to the list
subsChanged = doc.currentSubscriptions isnt JSON.stringify(emailSubs)
return callback?() unless emailChanged or subsChanged
params = {}
params.id = mail.MAILCHIMP_LIST_ID
params.email = if existingProps then {leid:existingProps.leid} else {email:doc.get('email')}
params.merge_vars = { groupings: [ {id: mail.MAILCHIMP_GROUP_ID, groups: newGroups} ] }
params.update_existing = true
params.double_optin = false
onSuccess = (data) ->
doc.set('mailChimp', data)
doc.updatedMailChimp = true
doc.save()
callback?()
onFailure = (error) ->
console.error 'failed to subscribe', error, callback?
log.error 'failed to subscribe', error, callback?
doc.updatedMailChimp = true
callback?()
mc.lists.subscribe params, onSuccess, onFailure
@ -75,9 +76,9 @@ UserSchema.pre('save', (next) ->
data =
email_id: sendwithus.templates.welcome_email
recipient:
address: @get 'email'
address: @get 'email'
sendwithus.api.send data, (err, result) ->
console.log 'error', err, 'result', result
log.error 'error', err, 'result', result if err
next()
)
@ -90,4 +91,4 @@ UserSchema.statics.hashPassword = (password) ->
shasum.update(salt + password)
shasum.digest('hex')
module.exports = User = mongoose.model('User', UserSchema)
module.exports = User = mongoose.model('User', UserSchema)