mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-01 07:40:22 -04:00
Merge branch 'master' of https://github.com/codecombat/codecombat
This commit is contained in:
commit
59f0d8a93e
26 changed files with 938 additions and 113 deletions
app
lib
locale
templates
views
server
server_config.jsserver_setup.coffee
|
@ -21,6 +21,7 @@ module.exports = class LevelBus extends Bus
|
|||
'thang-code-ran': 'onCodeRan'
|
||||
'level-show-victory': 'onVictory'
|
||||
'tome:spell-changed': 'onSpellChanged'
|
||||
'tome:spell-created': 'onSpellCreated'
|
||||
|
||||
constructor: ->
|
||||
super(arguments...)
|
||||
|
@ -91,12 +92,26 @@ module.exports = class LevelBus extends Bus
|
|||
code = @session.get('code')
|
||||
code ?= {}
|
||||
parts = e.spell.spellKey.split('/')
|
||||
|
||||
code[parts[0]] ?= {}
|
||||
code[parts[0]][parts[1]] = e.spell.getSource()
|
||||
@changedSessionProperties.code = true
|
||||
@session.set({'code': code})
|
||||
@saveSession()
|
||||
|
||||
onSpellCreated: (e) ->
|
||||
return unless @onPoint()
|
||||
spellTeam = e.spell.team
|
||||
@teamSpellMap[spellTeam] ?= []
|
||||
|
||||
unless e.spell.spellKey in @teamSpellMap[spellTeam]
|
||||
@teamSpellMap[spellTeam].push e.spell.spellKey
|
||||
@changedSessionProperties.teamSpells = true
|
||||
@session.set({'teamSpells': @teamSpellMap})
|
||||
@saveSession()
|
||||
|
||||
|
||||
|
||||
onScriptStateChanged: (e) ->
|
||||
return unless @onPoint()
|
||||
@fireScriptsRef?.update(e)
|
||||
|
@ -220,4 +235,12 @@ module.exports = class LevelBus extends Bus
|
|||
|
||||
destroy: ->
|
||||
super()
|
||||
@session.off 'change:multiplayer', @onMultiplayerChanged, @
|
||||
@session.off 'change:multiplayer', @onMultiplayerChanged, @
|
||||
|
||||
setTeamSpellMap: (spellMap) ->
|
||||
@teamSpellMap = spellMap
|
||||
console.log @teamSpellMap
|
||||
@changedSessionProperties.teamSpells = true
|
||||
@session.set({'teamSpells': @teamSpellMap})
|
||||
@saveSession()
|
||||
|
||||
|
|
|
@ -20,6 +20,9 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'db/*path': 'routeToServer'
|
||||
'file/*path': 'routeToServer'
|
||||
|
||||
'play/level/:levelID/leaderboard/:teamID/:startRank/:endRank': 'getPaginatedLevelRank'
|
||||
'play/level/:levelID/player/:playerID': 'getPlayerLevelInfo'
|
||||
|
||||
# most go through here
|
||||
'*name': 'general'
|
||||
|
||||
|
@ -27,6 +30,13 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
general: (name) ->
|
||||
@openRoute(name)
|
||||
|
||||
getPaginatedLevelRank: (levelID,teamID,startRank,endRank) ->
|
||||
return
|
||||
|
||||
getPlayerLevelInfo: (levelID,playerID) ->
|
||||
return
|
||||
|
||||
|
||||
editorModelView: (modelName, slugOrId, subview) ->
|
||||
modulePrefix = "views/editor/#{modelName}/"
|
||||
suffix = subview or (if slugOrId then 'edit' else 'home')
|
||||
|
|
|
@ -135,7 +135,6 @@ module.exports = class GoalManager extends CocoClass
|
|||
status: null # should eventually be either 'success', 'failure', or 'incomplete'
|
||||
keyFrame: 0 # when it became a 'success' or 'failure'
|
||||
}
|
||||
|
||||
@initGoalState(state, [goal.killThangs, goal.saveThangs], 'killed')
|
||||
@initGoalState(state, [goal.getToLocations?.who, goal.keepFromLocations?.who], 'arrived')
|
||||
@initGoalState(state, [goal.leaveOffSides?.who, goal.keepFromLeavingOffSides?.who], 'left')
|
||||
|
|
|
@ -31,21 +31,21 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
about: "Sobre"
|
||||
contact: "Contate-nos"
|
||||
twitter_follow: "Seguir"
|
||||
# employers: "Employers"
|
||||
employers: "Empregadores"
|
||||
|
||||
# versions:
|
||||
# save_version_title: "Save New Version"
|
||||
# new_major_version: "New Major Version"
|
||||
# cla_prefix: "To save changes, first you must agree to our"
|
||||
# cla_url: "CLA"
|
||||
# cla_suffix: "."
|
||||
# cla_agree: "I AGREE"
|
||||
versions:
|
||||
save_version_title: "Salvar nova versão"
|
||||
new_major_version: "Nova versão principal"
|
||||
cla_prefix: "Para salvar as modificações, primeiro você deve concordar com nosso"
|
||||
cla_url: "CLA"
|
||||
cla_suffix: "."
|
||||
cla_agree: "EU CONCORDO"
|
||||
|
||||
login:
|
||||
sign_up: "Criar conta"
|
||||
log_in: "Entrar"
|
||||
log_out: "Sair"
|
||||
recover: "recuperar sua conta"
|
||||
recover: "Recuperar sua conta"
|
||||
|
||||
recover:
|
||||
recover_account_title: "Recuperar conta"
|
||||
|
@ -122,7 +122,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
new_password_verify: "Confirmação"
|
||||
email_subscriptions: "Assinaturas para Notícias por Email"
|
||||
email_announcements: "Notícias"
|
||||
# email_notifications_description: "Get periodic notifications for your account."
|
||||
email_notifications_description: "Recebe notificações periódicas em sua conta."
|
||||
email_announcements_description: "Receba emails com as últimas notícias e desenvolvimentos do CodeCombat."
|
||||
contributor_emails: "Emails para as Classes de Contribuidores"
|
||||
contribute_prefix: "Estamos procurando pessoas para se juntar à nossa turma! Confira a nossa "
|
||||
|
@ -191,75 +191,75 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
tome_select_a_thang: "Selecione alguém para "
|
||||
tome_available_spells: "Feitiços Disponíveis"
|
||||
hud_continue: "Continue (tecle Shift+Space)"
|
||||
# spell_saved: "Spell Saved"
|
||||
spell_saved: "Feitiço Salvo"
|
||||
|
||||
# admin:
|
||||
# av_title: "Admin Views"
|
||||
# av_entities_sub_title: "Entities"
|
||||
# av_entities_users_url: "Users"
|
||||
# av_entities_active_instances_url: "Active Instances"
|
||||
# av_other_sub_title: "Other"
|
||||
# av_other_debug_base_url: "Base (for debugging base.jade)"
|
||||
# u_title: "User List"
|
||||
# lg_title: "Latest Games"
|
||||
admin:
|
||||
av_title: "Visualização de Administrador"
|
||||
av_entities_sub_title: "Entidades"
|
||||
av_entities_users_url: "Usuários"
|
||||
av_entities_active_instances_url: "Instâncias Ativas"
|
||||
av_other_sub_title: "Outro"
|
||||
av_other_debug_base_url: "Base (para debugar base.jade)"
|
||||
u_title: "Lista de Usuários"
|
||||
lg_title: "Últimos Jogos"
|
||||
|
||||
# editor:
|
||||
# main_title: "CodeCombat Editors"
|
||||
# main_description: "Build your own levels, campaigns, units and educational content. We provide all the tools you need!"
|
||||
# article_title: "Article Editor"
|
||||
# article_description: "Write articles that give players overviews of programming concepts which can be used across a variety of levels and campaigns."
|
||||
# thang_title: "Thang Editor"
|
||||
# thang_description: "Build units, defining their default logic, graphics and audio. Currently only supports importing Flash exported vector graphics."
|
||||
# level_title: "Level Editor"
|
||||
# level_description: "Includes the tools for scripting, uploading audio, and constructing custom logic to create all sorts of levels. Everything we use ourselves!"
|
||||
# security_notice: "Many major features in these editors are not currently enabled by default. As we improve the security of these systems, they will be made generally available. If you'd like to use these features sooner, "
|
||||
# contact_us: "contact us!"
|
||||
# hipchat_prefix: "You can also find us in our"
|
||||
# hipchat_url: "HipChat room."
|
||||
# level_some_options: "Some Options?"
|
||||
# level_tab_thangs: "Thangs"
|
||||
# level_tab_scripts: "Scripts"
|
||||
# level_tab_settings: "Settings"
|
||||
# level_tab_components: "Components"
|
||||
# level_tab_systems: "Systems"
|
||||
# level_tab_thangs_title: "Current Thangs"
|
||||
# level_tab_thangs_conditions: "Starting Conditions"
|
||||
# level_tab_thangs_add: "Add Thangs"
|
||||
# level_settings_title: "Settings"
|
||||
# level_component_tab_title: "Current Components"
|
||||
# level_component_btn_new: "Create New Component"
|
||||
# level_systems_tab_title: "Current Systems"
|
||||
# level_systems_btn_new: "Create New System"
|
||||
# level_systems_btn_add: "Add System"
|
||||
# level_components_title: "Back to All Thangs"
|
||||
# level_components_type: "Type"
|
||||
# level_component_edit_title: "Edit Component"
|
||||
# level_system_edit_title: "Edit System"
|
||||
# create_system_title: "Create New System"
|
||||
# new_component_title: "Create New Component"
|
||||
# new_component_field_system: "System"
|
||||
editor:
|
||||
main_title: "Editores do CodeCombat"
|
||||
main_description: "Construa seus próprios níveis, campanhas, unidades e conteúdo educacional. Nós fornecemos todas as ferramentas que você precisa!"
|
||||
article_title: "Editor de Artigo"
|
||||
article_description: "Escreva artigos que forneçam aos jogadores explicações sobre conceitos de programação que podem ser utilizados em diversos níveis e campanhas."
|
||||
thang_title: "Editor de Thang"
|
||||
thang_description: "Construa unidades, definindo sua lógica padrão, gráfico e áudio. Atualmente só é suportado importação de vetores gráficos exportados do Flash."
|
||||
level_title: "Editor de Niível"
|
||||
level_description: "Inclui as ferramentas para codificar, fazer o upload de áudio e construir uma lógica diferente para criar todos os tipos de níveis. Tudo o que nós mesmos utilizamos!"
|
||||
security_notice: "Muitos recursos principais nestes editores não estão habilitados por padrão atualmente. A maneira que melhoramos a segurança desses sistemas, eles serão colocados a disposição. Se você quiser utilizar um desses recursos mais rápido, "
|
||||
contact_us: "entre em contato!"
|
||||
hipchat_prefix: "Você também pode nos encontrar na nossa"
|
||||
hipchat_url: "Sala do HipChat."
|
||||
level_some_options: "Algumas Opções?"
|
||||
level_tab_thangs: "Thangs"
|
||||
level_tab_scripts: "Scripts"
|
||||
level_tab_settings: "Configurações"
|
||||
level_tab_components: "Componentes"
|
||||
level_tab_systems: "Sistemas"
|
||||
level_tab_thangs_title: "Thangs Atuais"
|
||||
level_tab_thangs_conditions: "Condições de Início"
|
||||
level_tab_thangs_add: "Adicionar Thangs"
|
||||
level_settings_title: "Configurações"
|
||||
level_component_tab_title: "Componentess Atuais"
|
||||
level_component_btn_new: "Criar Novo Componente"
|
||||
level_systems_tab_title: "Sistemas Atuais"
|
||||
level_systems_btn_new: "Criar Novo Sistema"
|
||||
level_systems_btn_add: "Adicionar Sistema"
|
||||
level_components_title: "Voltar para Lista de Thangs"
|
||||
level_components_type: "Tipo"
|
||||
level_component_edit_title: "Editar Componente"
|
||||
level_system_edit_title: "Editar Sistema"
|
||||
create_system_title: "Criar Novo Sistema"
|
||||
new_component_title: "Criar Novo Componente"
|
||||
new_component_field_system: "Sistema"
|
||||
|
||||
# article:
|
||||
# edit_btn_preview: "Preview"
|
||||
# edit_article_title: "Edit Article"
|
||||
article:
|
||||
edit_btn_preview: "Prever"
|
||||
edit_article_title: "Editar Artigo "
|
||||
|
||||
general:
|
||||
# and: "and"
|
||||
and: "e"
|
||||
or: "ou"
|
||||
name: "Nome"
|
||||
# body: "Body"
|
||||
# version: "Version"
|
||||
# commit_msg: "Commit Message"
|
||||
# version_history_for: "Version History for: "
|
||||
# results: "Results"
|
||||
# description: "Description"
|
||||
version: "Versão"
|
||||
commit_msg: "Mensagem do Commit"
|
||||
version_history_for: "Histórico de Versão para: "
|
||||
results: "Resultados"
|
||||
description: "Descrição"
|
||||
email: "Email"
|
||||
message: "Mensagem"
|
||||
|
||||
# about:
|
||||
# who_is_codecombat: "Who is CodeCombat?"
|
||||
# why_codecombat: "Why CodeCombat?"
|
||||
# who_description_prefix: "together started CodeCombat in 2013. We also created "
|
||||
about:
|
||||
who_is_codecombat: "Quem é CodeCombat?"
|
||||
why_codecombat: "Por que CodeCombat?"
|
||||
who_description_prefix: "juntos começamos o CodeCombat em 2013. Noós também criamos "
|
||||
# who_description_suffix: "in 2008, growing it to the #1 web and iOS application for learning to write Chinese and Japanese characters."
|
||||
# who_description_ending: "Now it's time to teach people to write code."
|
||||
# why_paragraph_1: "When making Skritter, George didn't know how to program and was constantly frustrated by his inability to implement his ideas. Afterwards, he tried learning, but the lessons were too slow. His housemate, wanting to reskill and stop teaching, tried Codecademy, but \"got bored.\" Each week another friend started Codecademy, then dropped off. We realized it was the same problem we'd solved with Skritter: people learning a skill via slow, intensive lessons when what they need is fast, extensive practice. We know how to fix that."
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
module.exports = nativeDescription: "简体中文", englishDescription: "Chinese (Simplified)", translation:
|
||||
common:
|
||||
loading: "读取中..."
|
||||
loading: "读取中……"
|
||||
saving: "保存中……"
|
||||
sending: "发送中..."
|
||||
sending: "发送中……"
|
||||
cancel: "退出"
|
||||
# save: "Save"
|
||||
delay_1_sec: "1 秒"
|
||||
|
@ -53,33 +53,33 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
|
||||
signup:
|
||||
# create_account_title: "Create Account to Save Progress"
|
||||
description: "这是免费的。简单易学:"
|
||||
description: "这是免费的,简单易学:"
|
||||
email_announcements: "通过邮件接收通知"
|
||||
coppa: "13 岁以上或非美国用户"
|
||||
coppa: "13岁以上或非美国用户"
|
||||
coppa_why: "为什么?"
|
||||
creating: "账户创建中..."
|
||||
creating: "账户创建中……"
|
||||
sign_up: "注册"
|
||||
log_in: "登录"
|
||||
|
||||
home:
|
||||
slogan: "通过玩儿游戏学到Javascript脚本语言"
|
||||
no_ie: "抱歉!Internet Explorer 9等更旧的预览器打不开此网站"
|
||||
no_ie: "抱歉!Internet Explorer 9等更旧的预览器打不开此网站。"
|
||||
no_mobile: "CodeCombat 不是针对手机设备设计的,所以可能不好用!"
|
||||
play: "玩"
|
||||
|
||||
play:
|
||||
choose_your_level: "选取难度"
|
||||
adventurer_prefix: "你可以选择以下任意关卡,或者讨论以上的关卡 "
|
||||
adventurer_forum: "冒险家论坛"
|
||||
adventurer_prefix: "你可以选择以下任意关卡,或者讨论以上的关卡。"
|
||||
adventurer_forum: "冒险者论坛"
|
||||
adventurer_suffix: "."
|
||||
campaign_beginner: "新手作战"
|
||||
campaign_beginner_description: "...在这里可以学到编程技巧。"
|
||||
campaign_beginner_description: "……在这里可以学到编程技巧。"
|
||||
campaign_dev: "随机困难关卡"
|
||||
campaign_dev_description: "...在这里你可以学到做一些复杂功能的接口。"
|
||||
campaign_dev_description: "……在这里你可以学到做一些复杂功能的接口。"
|
||||
campaign_multiplayer: "多人竞技场"
|
||||
campaign_multiplayer_description: "...在这里你可以和其他玩家们进行代码肉搏战。"
|
||||
campaign_multiplayer_description: "……在这里你可以和其他玩家们进行代码肉搏战。"
|
||||
campaign_player_created: "已创建的玩家"
|
||||
campaign_player_created_description: "...在这里你可以与你的小伙伴的创造力战斗 <a href=\"/contribute#artisan\">技术指导</a>."
|
||||
campaign_player_created_description: "……在这里你可以与你的小伙伴的创造力战斗 <a href=\"/contribute#artisan\">技术指导</a>."
|
||||
level_difficulty: "难度"
|
||||
|
||||
contact:
|
||||
|
@ -90,13 +90,13 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
contribute_suffix: "!"
|
||||
forum_prefix: "对任何公共部分,放手去干吧 "
|
||||
forum_page: "我们的论坛"
|
||||
forum_suffix: "代替。"
|
||||
forum_suffix: "代替 "
|
||||
send: "意见反馈"
|
||||
|
||||
diplomat_suggestion:
|
||||
title: "帮我们翻译CodeCombat"
|
||||
title: "帮我们翻译 CodeCombat"
|
||||
sub_heading: "我们需要您的语言技能"
|
||||
pitch_body: "我们开发了CodeCombat的英文版,但是现在我们的玩家遍布全球。很多人想玩中文(简体)版的,却不会说英语,所以如果你中英文都会,请考虑一下参加我们的翻译工作,帮忙把 CodeCombat 网站还有所有的关卡翻译成中文(简体)。"
|
||||
pitch_body: "我们开发了 CodeCombat 的英文版,但是现在我们的玩家遍布全球。很多人想玩中文(简体)版的,却不会说英语,所以如果你中英文都会,请考虑一下参加我们的翻译工作,帮忙把 CodeCombat 网站还有所有的关卡翻译成中文(简体)。"
|
||||
missing_translations: "未翻译的文本将显示为英文。"
|
||||
learn_more: "了解更多有关成为翻译人员的说明"
|
||||
subscribe_as_diplomat: "提交翻译人员申请"
|
||||
|
@ -122,7 +122,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
new_password_verify: "核实"
|
||||
email_subscriptions: "邮箱验证"
|
||||
email_announcements: "通知"
|
||||
# email_notifications_description: "Get periodic notifications for your account."
|
||||
email_notifications_description: "定期接受来自你的账户的通知。"
|
||||
email_announcements_description: "接收关于 CodeCombat 最近的新闻和发展的邮件。"
|
||||
contributor_emails: "贡献者通知"
|
||||
contribute_prefix: "我们在寻找志同道合的人!请到 "
|
||||
|
@ -153,7 +153,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
level_load_error: "关卡不能载入。"
|
||||
done: "完成"
|
||||
grid: "格子"
|
||||
customize_wizard: "自定义巫师"
|
||||
customize_wizard: "自定义向导"
|
||||
home: "主页"
|
||||
guide: "指南"
|
||||
multiplayer: "多人游戏"
|
||||
|
@ -168,7 +168,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
victory_title_suffix: " 完成"
|
||||
victory_sign_up: "保存进度"
|
||||
victory_sign_up_poke: "想保存你的代码?创建一个免费账户吧!"
|
||||
victory_rate_the_level: "评估关卡: "
|
||||
victory_rate_the_level: "评估关卡:"
|
||||
victory_play_next_level: "下一关"
|
||||
victory_go_home: "返回主页"
|
||||
victory_review: "给我们反馈!"
|
||||
|
@ -178,7 +178,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
multiplayer_link_description: "把这个链接告诉小伙伴们,一起玩吧。"
|
||||
multiplayer_hint_label: "提示:"
|
||||
multiplayer_hint: " 点击全选,然后按 Apple-C(苹果电脑)或 Ctrl-C 复制链接。"
|
||||
multiplayer_coming_soon: "多人游戏的更多特性!"
|
||||
multiplayer_coming_soon: "多人游戏的更多特性!"
|
||||
guide_title: "指南"
|
||||
tome_minion_spells: "助手的咒语"
|
||||
tome_read_only_spells: "只读的咒语"
|
||||
|
@ -190,7 +190,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
tome_select_spell: "选择一个法术"
|
||||
tome_select_a_thang: "选择人物来 "
|
||||
tome_available_spells: "可用的法术"
|
||||
hud_continue: "继续 (按 shift-空格)"
|
||||
hud_continue: "继续(按 shift-空格)"
|
||||
# spell_saved: "Spell Saved"
|
||||
|
||||
# admin:
|
||||
|
|
|
@ -57,7 +57,7 @@ block content
|
|||
| create an account
|
||||
span
|
||||
span(data-i18n="contribute.alert_account_message_suf")
|
||||
| first.
|
||||
| first.
|
||||
|
||||
label.checkbox(for="translator").well
|
||||
input(type='checkbox', name="translator", id="translator")
|
||||
|
|
|
@ -25,4 +25,6 @@ block content
|
|||
div.homepage_button
|
||||
a#beginner-campaign(href="/play/level/rescue-mission")
|
||||
canvas(width="125", height="150")
|
||||
button(data-i18n="home.play").btn.btn-warning.btn-lg.highlight Play
|
||||
button(data-i18n="home.play").btn.btn-warning.btn-lg.highlight Play
|
||||
if me.isAdmin()
|
||||
button.btn.btn-warning.btn-lg.highlight#simulate-button SIMULATE
|
|
@ -2,6 +2,10 @@ View = require 'views/kinds/RootView'
|
|||
template = require 'templates/home'
|
||||
WizardSprite = require 'lib/surface/WizardSprite'
|
||||
ThangType = require 'models/ThangType'
|
||||
LevelLoader = require 'lib/LevelLoader'
|
||||
God = require 'lib/God'
|
||||
|
||||
GoalManager = require 'lib/world/GoalManager'
|
||||
|
||||
module.exports = class HomeView extends View
|
||||
id: 'home-view'
|
||||
|
@ -10,6 +14,7 @@ module.exports = class HomeView extends View
|
|||
events:
|
||||
'mouseover #beginner-campaign': 'onMouseOverButton'
|
||||
'mouseout #beginner-campaign': 'onMouseOutButton'
|
||||
'click #simulate-button': 'onSimulateButtonClick'
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
|
@ -97,4 +102,91 @@ module.exports = class HomeView extends View
|
|||
|
||||
destroy: ->
|
||||
super()
|
||||
@wizardSprite?.destroy()
|
||||
@wizardSprite?.destroy()
|
||||
|
||||
onSimulateButtonClick: (e) =>
|
||||
$.get "/queue/scoring", (data) =>
|
||||
levelName = data.sessions[0].levelID
|
||||
#TODO: Refactor. So much refactor.
|
||||
world = {}
|
||||
god = new God()
|
||||
levelLoader = new LevelLoader(levelName, @supermodel, data.sessions[0].sessionID)
|
||||
levelLoader.once 'loaded-all', =>
|
||||
world = levelLoader.world
|
||||
level = levelLoader.level
|
||||
levelLoader.destroy()
|
||||
god.level = level.serialize @supermodel
|
||||
god.worldClassMap = world.classMap
|
||||
god.goalManager = new GoalManager(world)
|
||||
#move goals in here
|
||||
goalsToAdd = god.goalManager.world.scripts[0].noteChain[0].goals.add
|
||||
god.goalManager.goals = goalsToAdd
|
||||
god.goalManager.goalStates =
|
||||
"destroy-humans":
|
||||
keyFrame: 0
|
||||
killed:
|
||||
"Human Base": false
|
||||
status: "incomplete"
|
||||
"destroy-ogres":
|
||||
keyFrame:0
|
||||
killed:
|
||||
"Ogre Base": false
|
||||
status: "incomplete"
|
||||
god.spells = @filterProgrammableComponents level.attributes.thangs, @generateSpellToSourceMap data.sessions
|
||||
god.createWorld()
|
||||
|
||||
Backbone.Mediator.subscribe 'god:new-world-created', @onWorldCreated, @
|
||||
|
||||
onWorldCreated: (data) ->
|
||||
console.log "GOAL STATES"
|
||||
console.log data
|
||||
|
||||
|
||||
filterProgrammableComponents: (thangs, spellToSourceMap) =>
|
||||
spells = {}
|
||||
for thang in thangs
|
||||
isTemplate = false
|
||||
for component in thang.components
|
||||
if component.config? and _.has component.config,'programmableMethods'
|
||||
for methodName, method of component.config.programmableMethods
|
||||
if typeof method is 'string'
|
||||
isTemplate = true
|
||||
break
|
||||
|
||||
pathComponents = [thang.id,methodName]
|
||||
pathComponents[0] = _.string.slugify pathComponents[0]
|
||||
spellKey = pathComponents.join '/'
|
||||
spells[spellKey] ?= {}
|
||||
spells[spellKey].thangs ?= {}
|
||||
spells[spellKey].name = methodName
|
||||
thangID = _.string.slugify thang.id
|
||||
spells[spellKey].thangs[thang.id] ?= {}
|
||||
spells[spellKey].thangs[thang.id].aether = @createAether methodName, method
|
||||
if spellToSourceMap[thangID]? then source = spellToSourceMap[thangID][methodName] else source = ""
|
||||
spells[spellKey].thangs[thang.id].aether.transpile source
|
||||
if isTemplate
|
||||
break
|
||||
|
||||
spells
|
||||
|
||||
createAether : (methodName, method) ->
|
||||
aetherOptions =
|
||||
functionName: methodName
|
||||
protectAPI: false
|
||||
includeFlow: false
|
||||
return new Aether aetherOptions
|
||||
|
||||
generateSpellToSourceMap: (sessions) ->
|
||||
spellKeyToSourceMap = {}
|
||||
spellSources = {}
|
||||
for session in sessions
|
||||
teamSpells = session.teamSpells[session.team]
|
||||
_.merge spellSources, _.pick(session.code, teamSpells)
|
||||
|
||||
#merge common ones, this overwrites until the last session
|
||||
commonSpells = session.teamSpells["common"]
|
||||
if commonSpells?
|
||||
_.merge spellSources, _.pick(session.code, commonSpells)
|
||||
|
||||
spellSources
|
||||
|
||||
|
|
|
@ -26,12 +26,16 @@ module.exports = class Spell
|
|||
@view.render() # Get it ready and code loaded in advance
|
||||
@tabView = new SpellListTabEntryView spell: @, supermodel: @supermodel
|
||||
@tabView.render()
|
||||
@team = @permissions.readwrite[0] ? "common"
|
||||
Backbone.Mediator.publish 'tome:spell-created', spell: @
|
||||
|
||||
|
||||
destroy: ->
|
||||
@view.destroy()
|
||||
@tabView.destroy()
|
||||
@thangs = null
|
||||
|
||||
|
||||
addThang: (thang) ->
|
||||
if @thangs[thang.id]
|
||||
@thangs[thang.id].thang = thang
|
||||
|
|
|
@ -62,6 +62,7 @@ module.exports = class TomeView extends View
|
|||
@spellList = @insertSubView new SpellListView spells: @spells, supermodel: @supermodel
|
||||
@thangList = @insertSubView new ThangListView spells: @spells, thangs: @options.thangs, supermodel: @supermodel
|
||||
@castButton = @insertSubView new CastButtonView spells: @spells
|
||||
@teamSpellMap = @generateTeamSpellMap(@spells)
|
||||
else
|
||||
@cast()
|
||||
console.warn "Warning: There are no Programmable Thangs in this level, which makes it unplayable."
|
||||
|
@ -74,6 +75,21 @@ module.exports = class TomeView extends View
|
|||
@thangList.adjustThangs @spells, thangs
|
||||
@spellList.adjustSpells @spells
|
||||
|
||||
generateTeamSpellMap: (spellObject) ->
|
||||
teamSpellMap = {}
|
||||
for spellName, spell of spellObject
|
||||
teamName = spell.team
|
||||
teamSpellMap[teamName] ?= []
|
||||
|
||||
spellNameElements = spellName.split '/'
|
||||
thangName = spellNameElements[0]
|
||||
spellName = spellNameElements[1]
|
||||
|
||||
teamSpellMap[teamName].push thangName if thangName not in teamSpellMap[teamName]
|
||||
|
||||
return teamSpellMap
|
||||
|
||||
|
||||
createSpells: (programmableThangs, world) ->
|
||||
pathPrefixComponents = ['play', 'level', @options.levelID, @options.session.id, 'code']
|
||||
@spells ?= {}
|
||||
|
|
|
@ -360,6 +360,7 @@ module.exports = class PlayLevelView extends View
|
|||
register: ->
|
||||
@bus = LevelBus.get(@levelID, @session.id)
|
||||
@bus.setSession(@session)
|
||||
@bus.setTeamSpellMap @tome.teamSpellMap
|
||||
@bus.connect() if @session.get('multiplayer')
|
||||
|
||||
onSessionWillSave: (e) ->
|
||||
|
@ -391,4 +392,4 @@ module.exports = class PlayLevelView extends View
|
|||
@bus?.destroy()
|
||||
#@instance.save() unless @instance.loading
|
||||
console.profileEnd?() if PROFILE_ME
|
||||
@session.off 'change:multiplayer', @onMultiplayerChanged, @
|
||||
@session.off 'change:multiplayer', @onMultiplayerChanged, @
|
||||
|
|
|
@ -59,7 +59,9 @@
|
|||
"express-useragent": "~0.0.9",
|
||||
"gridfs-stream": "0.4.x",
|
||||
"stream-buffers": "0.2.x",
|
||||
"sendwithus": "2.0.x"
|
||||
"sendwithus": "2.0.x",
|
||||
"aws-sdk":"~2.0.0",
|
||||
"bayesian-battle":"0.0.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jade": "0.33.x",
|
||||
|
|
|
@ -25,14 +25,3 @@ createAndConfigureApp = ->
|
|||
serverSetup.setupRoutes app
|
||||
app
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -7,18 +7,22 @@ testing = '--unittest' in process.argv
|
|||
|
||||
|
||||
module.exports.connect = () ->
|
||||
address = module.exports.generateMongoConnectionString()
|
||||
winston.info "Connecting to Mongo with connection string #{address}"
|
||||
|
||||
mongoose.connect address
|
||||
mongoose.connection.once 'open', -> Grid.gfs = Grid(mongoose.connection.db, mongoose.mongo)
|
||||
|
||||
|
||||
module.exports.generateMongoConnectionString = ->
|
||||
if config.mongo.mongoose_replica_string
|
||||
address = config.mongo.mongoose_replica_string
|
||||
winston.info "Connecting to replica set: #{address}"
|
||||
else
|
||||
dbName = config.mongo.db
|
||||
dbName += '_unittest' if testing
|
||||
address = config.mongo.host + ":" + config.mongo.port
|
||||
if config.mongo.username and config.mongo.password
|
||||
address = config.mongo.username + ":" + config.mongo.password + "@" + address
|
||||
# address = config.mongo.username + "@" + address # if connecting to production server
|
||||
address = "mongodb://#{address}/#{dbName}"
|
||||
winston.info "Connecting to standalone server #{address}"
|
||||
mongoose.connect address
|
||||
mongoose.connection.once 'open', ->
|
||||
Grid.gfs = Grid(mongoose.connection.db, mongoose.mongo)
|
||||
|
||||
return address
|
|
@ -33,3 +33,11 @@ module.exports.badInput = (res, message='Unprocessable Entity. Bad Input.') ->
|
|||
module.exports.serverError = (res, message='Internal Server Error') ->
|
||||
res.send 500, message
|
||||
res.end()
|
||||
|
||||
module.exports.gatewayTimeoutError = (res, message="Gateway timeout") ->
|
||||
res.send 504, message
|
||||
res.end()
|
||||
|
||||
module.exports.clientTimeout = (res, message="The server did not recieve the client response in a timely manner") ->
|
||||
res.send 408, message
|
||||
res.end()
|
|
@ -33,4 +33,5 @@ module.exports.routes =
|
|||
'routes/languages'
|
||||
'routes/mail'
|
||||
'routes/sprites'
|
||||
'routes/queue'
|
||||
]
|
||||
|
|
283
server/commons/queue.coffee
Normal file
283
server/commons/queue.coffee
Normal file
|
@ -0,0 +1,283 @@
|
|||
config = require '../../server_config'
|
||||
log = require 'winston'
|
||||
mongoose = require 'mongoose'
|
||||
async = require 'async'
|
||||
aws = require 'aws-sdk'
|
||||
db = require './database'
|
||||
mongoose = require 'mongoose'
|
||||
events = require 'events'
|
||||
crypto = require 'crypto'
|
||||
|
||||
module.exports.queueClient = undefined
|
||||
|
||||
defaultMessageVisibilityTimeoutInSeconds = 20
|
||||
defaultMessageReceiptTimeout = 10
|
||||
|
||||
|
||||
module.exports.initializeQueueClient = (cb) ->
|
||||
module.exports.queueClient = generateQueueClient() unless queueClient?
|
||||
|
||||
cb?()
|
||||
|
||||
|
||||
generateQueueClient = ->
|
||||
#if config.queue.accessKeyId
|
||||
if false #TODO: Change this in production
|
||||
queueClient = new SQSQueueClient()
|
||||
else
|
||||
queueClient = new MongoQueueClient()
|
||||
|
||||
|
||||
class SQSQueueClient
|
||||
registerQueue: (queueName, options, callback) ->
|
||||
queueCreationOptions =
|
||||
QueueName: queueName
|
||||
|
||||
@sqs.createQueue queueCreationOptions, (err,data) =>
|
||||
@_logAndThrowFatalException "There was an error creating a new SQS queue, reason: #{JSON.stringify err}" if err?
|
||||
|
||||
newQueue = new SQSQueue queueName, data.QueueUrl, @sqs
|
||||
|
||||
callback? err, newQueue
|
||||
|
||||
|
||||
constructor: ->
|
||||
@_configure()
|
||||
@sqs = @_generateSQSInstance()
|
||||
|
||||
|
||||
_configure: ->
|
||||
aws.config.update
|
||||
accessKeyId: config.queue.accessKeyId
|
||||
secretAccessKey: config.queue.secretAccessKey
|
||||
region: config.queue.region
|
||||
|
||||
|
||||
_generateSQSInstance: -> new aws.SQS()
|
||||
|
||||
|
||||
_logAndThrowFatalException: (errorMessage) ->
|
||||
log.error errorMessage
|
||||
throw new Error errorMessage
|
||||
|
||||
|
||||
|
||||
class SQSQueue extends events.EventEmitter
|
||||
constructor: (@queueName, @queueUrl, @sqs) ->
|
||||
|
||||
|
||||
subscribe: (eventName, callback) -> @on eventName, callback
|
||||
unsubscribe: (eventName, callback) -> @removeListener eventName, callback
|
||||
|
||||
|
||||
receiveMessage: (callback) ->
|
||||
queueReceiveOptions =
|
||||
QueueUrl: @queueUrl
|
||||
WaitTimeSeconds: defaultMessageReceiptTimeout
|
||||
|
||||
@sqs.receiveMessage queueReceiveOptions, (err, data) =>
|
||||
if err?
|
||||
@emit 'error',err,originalData
|
||||
else
|
||||
originalData = data
|
||||
data = new SQSMessage originalData, this
|
||||
@emit 'message',err,data
|
||||
|
||||
callback? err,data
|
||||
|
||||
|
||||
deleteMessage: (receiptHandle, callback) ->
|
||||
queueDeletionOptions =
|
||||
QueueUrl: @queueUrl
|
||||
ReceiptHandle: receiptHandle
|
||||
|
||||
@sqs.deleteMessage queueDeletionOptions, (err, data) =>
|
||||
if err? then @emit 'error',err,data else @emit 'message',err,data
|
||||
|
||||
callback? err,data
|
||||
|
||||
|
||||
changeMessageVisibilityTimeout: (secondsFromNow, receiptHandle, callback) ->
|
||||
messageVisibilityTimeoutOptions =
|
||||
QueueUrl: @queueUrl
|
||||
ReceiptHandle: receiptHandle
|
||||
VisibilityTimeout: secondsFromNow
|
||||
|
||||
@sqs.changeMessageVisibility messageVisibilityTimeoutOptions, (err, data) =>
|
||||
if err? then @emit 'error',err,data else @emit 'edited',err,data
|
||||
|
||||
callback? err,data
|
||||
|
||||
|
||||
sendMessage: (messageBody, delaySeconds, callback) ->
|
||||
queueSendingOptions =
|
||||
QueueUrl: @queueUrl
|
||||
MessageBody: messageBody
|
||||
DelaySeconds: delaySeconds
|
||||
|
||||
@sqs.sendMessage queueSendingOptions, (err, data) =>
|
||||
if err? then @emit 'error',err,data else @emit 'sent',err, data
|
||||
|
||||
callback? err,data
|
||||
|
||||
|
||||
listenForever: => async.forever (asyncCallback) => @receiveMessage (err, data) -> asyncCallback(null)
|
||||
|
||||
|
||||
class SQSMessage
|
||||
constructor: (@originalMessage, @parentQueue) ->
|
||||
|
||||
isEmpty: -> not @originalMessage.Messages?[0]?
|
||||
|
||||
getBody: -> @originalMessage.Messages[0].Body
|
||||
|
||||
getID: -> @originalMessage.Messages[0].MessageId
|
||||
|
||||
removeFromQueue: (callback) -> @parentQueue.deleteMessage @getReceiptHandle(), callback
|
||||
|
||||
requeue: (callback) -> @parentQueue.changeMessageVisibilityTimeout 0, @getReceiptHandle(), callback
|
||||
|
||||
changeMessageVisibilityTimeout: (secondsFromFunctionCall, callback) ->
|
||||
@parentQueue.changeMessageVisibilityTimeout secondsFromFunctionCall,@getReceiptHandle(), callback
|
||||
|
||||
getReceiptHandle: -> @originalMessage.Messages[0].ReceiptHandle
|
||||
|
||||
|
||||
|
||||
|
||||
class MongoQueueClient
|
||||
registerQueue: (queueName, options, callback) ->
|
||||
newQueue = new MongoQueue queueName,options,@messageModel
|
||||
callback(null, newQueue)
|
||||
|
||||
|
||||
constructor: ->
|
||||
@_configure()
|
||||
@_createMongoConnection()
|
||||
@messageModel = @_generateMessageModel()
|
||||
|
||||
|
||||
_configure: -> @databaseAddress = db.generateMongoConnectionString()
|
||||
|
||||
|
||||
_createMongoConnection: ->
|
||||
@mongooseConnection = mongoose.createConnection @databaseAddress
|
||||
@mongooseConnection.on 'error', -> log.error "There was an error connecting to the queue in MongoDB"
|
||||
@mongooseConnection.once 'open', -> log.info "Successfully connected to MongoDB queue!"
|
||||
|
||||
|
||||
_generateMessageModel: ->
|
||||
schema = new mongoose.Schema
|
||||
messageBody: Object,
|
||||
queue: {type: String, index:true}
|
||||
scheduledVisibilityTime: {type: Date, index: true}
|
||||
receiptHandle: {type: String, index: true}
|
||||
|
||||
@mongooseConnection.model 'messageQueue',schema
|
||||
|
||||
|
||||
class MongoQueue extends events.EventEmitter
|
||||
constructor: (queueName, options, messageModel) ->
|
||||
@Message = messageModel
|
||||
@queueName = queueName
|
||||
|
||||
|
||||
subscribe: (eventName, callback) -> @on eventName, callback
|
||||
unsubscribe: (eventName, callback) -> @removeListener eventName, callback
|
||||
|
||||
|
||||
receiveMessage: (callback) ->
|
||||
conditions =
|
||||
queue: @queueName
|
||||
scheduledVisibilityTime:
|
||||
$lt: new Date()
|
||||
|
||||
options =
|
||||
sort: 'scheduledVisibilityTime'
|
||||
|
||||
update =
|
||||
$set:
|
||||
receiptHandle: @_generateRandomReceiptHandle()
|
||||
scheduledVisibilityTime: @_constructDefaultVisibilityTimeoutDate()
|
||||
|
||||
@Message.findOneAndUpdate conditions, update, options, (err, data) =>
|
||||
return @emit 'error',err,data if err?
|
||||
|
||||
originalData = data
|
||||
data = new MongoMessage originalData, this
|
||||
@emit 'message',err,data
|
||||
callback? err,data
|
||||
|
||||
|
||||
deleteMessage: (receiptHandle, callback) ->
|
||||
conditions =
|
||||
queue: @queueName
|
||||
receiptHandle: receiptHandle
|
||||
scheduledVisibilityTime:
|
||||
$lt: new Date()
|
||||
|
||||
@Message.findOneAndRemove conditions, {}, (err, data) =>
|
||||
if err? then @emit 'error',err,data else @emit 'delete',err,data
|
||||
|
||||
callback? err,data
|
||||
|
||||
|
||||
sendMessage: (messageBody, delaySeconds, callback) ->
|
||||
messageToSend = new @Message
|
||||
messageBody: messageBody
|
||||
queue: @queueName
|
||||
scheduledVisibilityTime: @_constructDefaultVisibilityTimeoutDate delaySeconds
|
||||
|
||||
messageToSend.save (err,data) =>
|
||||
if err? then @emit 'error',err,data else @emit 'sent',err, data
|
||||
callback? err,data
|
||||
|
||||
changeMessageVisibilityTimeout: (secondsFromNow, receiptHandle, callback) ->
|
||||
conditions =
|
||||
queue: @queueName
|
||||
receiptHandle: receiptHandle
|
||||
scheduledVisibilityTime:
|
||||
$lt: new Date()
|
||||
|
||||
update =
|
||||
$set:
|
||||
scheduledVisibilityTime: @_constructDefaultVisibilityTimeoutDate secondsFromNow
|
||||
|
||||
@Message.findOneAndUpdate conditions, update, (err, data) =>
|
||||
if err? then @emit 'error',err,data else @emit 'update',err,data
|
||||
|
||||
callback? err, data
|
||||
|
||||
|
||||
listenForever: => async.forever (asyncCallback) => @recieveMessage (err, data) -> asyncCallback(null)
|
||||
|
||||
|
||||
_constructDefaultVisibilityTimeoutDate: (timeoutSeconds) ->
|
||||
timeoutSeconds ?= defaultMessageVisibilityTimeoutInSeconds
|
||||
newDate = new Date()
|
||||
newDate = new Date(newDate.getTime() + 1000 * timeoutSeconds)
|
||||
|
||||
newDate
|
||||
|
||||
|
||||
_generateRandomReceiptHandle: -> crypto.randomBytes(20).toString('hex')
|
||||
|
||||
|
||||
|
||||
class MongoMessage
|
||||
constructor: (@originalMessage, @parentQueue) ->
|
||||
|
||||
isEmpty: -> not @originalMessage
|
||||
|
||||
getBody: -> @originalMessage.messageBody
|
||||
|
||||
getID: -> @originalMesage._id
|
||||
|
||||
removeFromQueue: (callback) -> @parentQueue.deleteMessage @getReceiptHandle(), callbacks
|
||||
|
||||
requeue: (callback) -> @parentQueue.changeMessageVisibilityTimeout 0, @getReceiptHandle(), callback
|
||||
|
||||
changeMessageVisibilityTimeout: (secondsFromFunctionCall, callback) ->
|
||||
@parentQueue.changeMessageVisibilityTimeout secondsFromFunctionCall,@getReceiptHandle(), callback
|
||||
|
||||
getReceiptHandle: -> @originalMessage.receiptHandle
|
|
@ -7,7 +7,7 @@ class LevelSessionHandler extends Handler
|
|||
modelClass: LevelSession
|
||||
editableProperties: ['multiplayer', 'players', 'code', 'completed', 'state',
|
||||
'levelName', 'creatorName', 'levelID', 'screenshot',
|
||||
'chat']
|
||||
'chat', 'teamSpells']
|
||||
|
||||
getByRelationship: (req, res, args...) ->
|
||||
return @sendNotFoundError(res) unless args.length is 2 and args[1] is 'active'
|
||||
|
|
|
@ -55,9 +55,18 @@ _.extend LevelSessionSchema.properties,
|
|||
|
||||
# TODO: specify this more
|
||||
code: { type: 'object' }
|
||||
teamSpells:
|
||||
type: 'object'
|
||||
additionalProperties:
|
||||
type: 'array'
|
||||
|
||||
|
||||
players: { type: 'object' }
|
||||
chat: { type: 'array' }
|
||||
|
||||
meanStrength: {type: 'number', default: 25}
|
||||
standardDeviation: {type:'number', default:25/3, minimum: 0}
|
||||
totalScore: {type: 'number', default: 10}
|
||||
|
||||
|
||||
c.extendBasicProperties LevelSessionSchema, 'level.session'
|
||||
|
|
306
server/queues/scoring.coffee
Normal file
306
server/queues/scoring.coffee
Normal file
|
@ -0,0 +1,306 @@
|
|||
config = require '../../server_config'
|
||||
log = require 'winston'
|
||||
mongoose = require 'mongoose'
|
||||
async = require 'async'
|
||||
errors = require '../commons/errors'
|
||||
aws = require 'aws-sdk'
|
||||
db = require './../routes/db'
|
||||
mongoose = require 'mongoose'
|
||||
queues = require '../commons/queue'
|
||||
LevelSession = require '../levels/sessions/LevelSession'
|
||||
TaskLog = require './task/ScoringTask'
|
||||
bayes = new (require 'bayesian-battle')()
|
||||
|
||||
scoringTaskQueue = undefined
|
||||
scoringTaskTimeoutInSeconds = 400
|
||||
|
||||
|
||||
module.exports.setup = (app) -> connectToScoringQueue()
|
||||
|
||||
connectToScoringQueue = ->
|
||||
queues.initializeQueueClient ->
|
||||
queues.queueClient.registerQueue "scoring", {}, (err,data) ->
|
||||
throwScoringQueueRegistrationError(err) if err?
|
||||
scoringTaskQueue = data
|
||||
log.info "Connected to scoring task queue!"
|
||||
|
||||
throwScoringQueueRegistrationError = (error) ->
|
||||
log.error "There was an error registering the scoring queue: #{error}"
|
||||
throw new Error "There was an error registering the scoring queue."
|
||||
|
||||
module.exports.createNewTask = (req, res) ->
|
||||
scoringTaskQueue.sendMessage req.body, 0, (err, data) ->
|
||||
return errors.badInput res, "There was an error creating the message, reason: #{err}" if err?
|
||||
|
||||
res.send data
|
||||
res.end()
|
||||
|
||||
|
||||
module.exports.dispatchTaskToConsumer = (req, res) ->
|
||||
userID = getUserIDFromRequest req,res
|
||||
return errors.forbidden res, "You need to be logged in to simulate games" if isUserAnonymous req
|
||||
|
||||
scoringTaskQueue.receiveMessage (taskQueueReceiveError, message) ->
|
||||
if (not message?) or message.isEmpty() or taskQueueReceiveError?
|
||||
return errors.gatewayTimeoutError res, "No messages were receieved from the queue. Msg:#{taskQueueReceiveError}"
|
||||
|
||||
messageBody = parseTaskQueueMessage req, res, message
|
||||
return errors.serverError res, "There was an error parsing the queue message" unless messageBody?
|
||||
|
||||
constructTaskObject messageBody, (taskConstructionError, taskObject) ->
|
||||
return errors.serverError res, "There was an error constructing the scoring task" if taskConstructionError?
|
||||
|
||||
message.changeMessageVisibilityTimeout scoringTaskTimeoutInSeconds
|
||||
|
||||
constructTaskLogObject userID,message.getReceiptHandle(), (taskLogError, taskLogObject) ->
|
||||
return errors.serverError res, "There was an error creating the task log object." if taskLogError?
|
||||
|
||||
setTaskObjectTaskLogID taskObject, taskLogObject._id
|
||||
|
||||
sendResponseObject req, res, taskObject
|
||||
|
||||
|
||||
getUserIDFromRequest = (req) -> if req.user? then return req.user._id else return null
|
||||
|
||||
|
||||
isUserAnonymous = (req) -> if req.user? then return req.user.anonymous else return true
|
||||
|
||||
|
||||
parseTaskQueueMessage = (req, res, message) ->
|
||||
try
|
||||
if typeof message.getBody() is "object" then return message.getBody()
|
||||
|
||||
return messageBody = JSON.parse message.getBody()
|
||||
catch e
|
||||
sendResponseObject req, res, {"error":"There was an error parsing the task.Error: #{e}" }
|
||||
return null
|
||||
|
||||
constructTaskObject = (taskMessageBody, callback) ->
|
||||
async.map taskMessageBody.sessions, getSessionInformation, (err, sessions) ->
|
||||
return callback err, data if err?
|
||||
|
||||
taskObject =
|
||||
"messageGenerated": Date.now()
|
||||
"sessions": []
|
||||
|
||||
for session in sessions
|
||||
sessionInformation =
|
||||
"sessionID": session.sessionID
|
||||
"sessionChangedTime": session.changed
|
||||
"team": session.team ? "No team"
|
||||
"code": session.code
|
||||
"teamSpells": session.teamSpells ? {}
|
||||
"levelID": session.levelID
|
||||
|
||||
taskObject.sessions.push sessionInformation
|
||||
callback err, taskObject
|
||||
|
||||
|
||||
getSessionInformation = (sessionIDString, callback) ->
|
||||
LevelSession.findOne {"_id": sessionIDString }, (err, session) ->
|
||||
return callback err, {"error":"There was an error retrieving the session."} if err?
|
||||
|
||||
session = session.toObject()
|
||||
sessionInformation =
|
||||
"sessionID": session._id
|
||||
"code": _.cloneDeep session.code
|
||||
"changed": session.changed
|
||||
"creator": session.creator
|
||||
"team": session.team
|
||||
"teamSpells": session.teamSpells
|
||||
"levelID": session.levelID
|
||||
|
||||
callback err, sessionInformation
|
||||
|
||||
|
||||
constructTaskLogObject = (calculatorUserID, messageIdentifierString, callback) ->
|
||||
taskLogObject = new TaskLog
|
||||
"createdAt": new Date()
|
||||
"calculator":calculatorUserID
|
||||
"sentDate": Date.now()
|
||||
"messageIdentifierString":messageIdentifierString
|
||||
|
||||
taskLogObject.save callback
|
||||
|
||||
|
||||
setTaskObjectTaskLogID = (taskObject, taskLogObjectID) -> taskObject.taskID = taskLogObjectID
|
||||
|
||||
|
||||
sendResponseObject = (req,res,object) ->
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.send(object)
|
||||
res.end()
|
||||
|
||||
module.exports.processTaskResult = (req, res) ->
|
||||
clientResponseObject = verifyClientResponse req.body, res
|
||||
|
||||
if clientResponseObject?
|
||||
TaskLog.findOne {"_id": clientResponseObject.taskID}, (err, taskLog) ->
|
||||
return errors.serverError res, "There was an error retrieiving the task log object" if err?
|
||||
|
||||
taskLogJSON = taskLog.toObject()
|
||||
|
||||
return errors.badInput res, "That computational task has already been performed" if taskLogJSON.calculationTimeMS
|
||||
return handleTimedOutTask req, res, clientResponseObject if hasTaskTimedOut taskLogJSON.sentDate
|
||||
|
||||
logTaskComputation clientResponseObject, taskLog, (loggingError) ->
|
||||
if loggingError?
|
||||
return errors.serverError res, "There as a problem logging the task computation: #{loggingError}"
|
||||
|
||||
updateScores clientResponseObject, (updatingScoresError, newScores) ->
|
||||
if updatingScoresError?
|
||||
return errors.serverError res, "There was an error updating the scores.#{updatingScoresError}"
|
||||
|
||||
sendResponseObject req, res, {"message":"The scores were updated successfully!"}
|
||||
|
||||
|
||||
|
||||
|
||||
hasTaskTimedOut = (taskSentTimestamp) -> taskSentTimestamp + scoringTaskTimeoutInSeconds * 1000 < Date.now()
|
||||
|
||||
|
||||
handleTimedOutTask = (req, res, taskBody) -> errors.clientTimeout res, "The results weren't provided within the timeout"
|
||||
|
||||
|
||||
verifyClientResponse = (responseObject, res) ->
|
||||
unless typeof responseObject is "object"
|
||||
errors.badInput res, "The response to that query is required to be a JSON object."
|
||||
null
|
||||
else
|
||||
responseObject
|
||||
|
||||
|
||||
logTaskComputation = (taskObject,taskLogObject, callback) ->
|
||||
taskLogObject.calculationTimeMS = taskObject.calculationTimeMS
|
||||
taskLogObject.sessions = taskObject.sessions
|
||||
taskLogObject.save callback
|
||||
|
||||
|
||||
updateScores = (taskObject,callback) ->
|
||||
sessionIDs = _.pluck taskObject.sessions, 'sessionID'
|
||||
|
||||
async.map sessionIDs, retrieveOldScoreMetrics, (err, oldScores) ->
|
||||
callback err, {"error": "There was an error retrieving the old scores"} if err?
|
||||
|
||||
oldScoreArray = _.toArray putRankingFromMetricsIntoScoreObject taskObject, oldScores
|
||||
|
||||
newScoreArray = bayes.updatePlayerSkills oldScoreArray
|
||||
|
||||
saveNewScoresToDatabase newScoreArray, callback
|
||||
|
||||
|
||||
saveNewScoresToDatabase = (newScoreArray, callback) ->
|
||||
async.eachSeries newScoreArray, updateScoreInSession, (err) ->
|
||||
if err? then callback err, null else callback err, {"message":"All scores were saved successfully."}
|
||||
|
||||
|
||||
updateScoreInSession = (scoreObject,callback) ->
|
||||
sessionObjectQuery =
|
||||
"_id": scoreObject.id
|
||||
|
||||
LevelSession.findOne sessionObjectQuery, (err, session) ->
|
||||
return callback err, null if err?
|
||||
|
||||
session.meanStrength = scoreObject.meanStrength
|
||||
session.standardDeviation = scoreObject.standardDeviation
|
||||
session.totalScore = scoreObject.meanStrength - 1.8 * scoreObject.standardDeviation
|
||||
|
||||
log.info "Saving session #{session._id}!"
|
||||
|
||||
session.save callback
|
||||
|
||||
|
||||
putRankingFromMetricsIntoScoreObject = (taskObject,scoreObject) ->
|
||||
scoreObject = _.indexBy scoreObject, 'id'
|
||||
|
||||
for session in taskObject.sessions
|
||||
scoreObject[session.sessionID].gameRanking = session.metrics.rank
|
||||
|
||||
scoreObject
|
||||
|
||||
retrieveOldScoreMetrics = (sessionID, callback) ->
|
||||
sessionQuery =
|
||||
"_id":sessionID
|
||||
|
||||
LevelSession.findOne sessionQuery, (err, session) ->
|
||||
return callback err, {"error":"There was an error retrieving the session."} if err?
|
||||
|
||||
defaultScore = (25 - 1.8*(25/3))
|
||||
defaultStandardDeviation = 25/3
|
||||
|
||||
oldScoreObject =
|
||||
"standardDeviation":session.standardDeviation ? defaultStandardDeviation
|
||||
"meanStrength":session.meanStrength ? 25
|
||||
"totalScore":session.totalScore ? defaultScore
|
||||
"id": sessionID
|
||||
|
||||
callback err, oldScoreObject
|
||||
|
||||
|
||||
|
||||
|
||||
###Sample Messages
|
||||
sampleQueueMessage =
|
||||
{
|
||||
"sessions": ["52dea9b77e486eeb97000001","52d981a73cf02dcf260003cb"]
|
||||
}
|
||||
|
||||
sampleUndoneTaskObject =
|
||||
"taskID": "507f191e810c19729de860ea"
|
||||
"sessions" : [
|
||||
{
|
||||
"ID":"52dfeb17c8b5f435c7000025"
|
||||
"sessionChangedTime": "2014-01-22T16:28:12.450Z"
|
||||
"team":"humans"
|
||||
"code": "code goes here"
|
||||
},
|
||||
{
|
||||
"ID":"51eb2714fa058cb20d00fedg"
|
||||
"sessionChangedTime": "2014-01-22T16:28:12.450Z"
|
||||
"team":"ogres"
|
||||
"code": "code goes here"
|
||||
}
|
||||
]
|
||||
sampleResponseObject =
|
||||
"taskID": "507f191e810c19729de860ea"
|
||||
"calculationTime":3201
|
||||
"sessions": [
|
||||
{
|
||||
"ID":"52dfeb17c8b5f435c7000025"
|
||||
"sessionChangedTime": "2014-01-22T16:28:12.450Z"
|
||||
"metrics": {
|
||||
"rank":2
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID":"51eb2714fa058cb20d00fedg"
|
||||
"sessionChangedTime": "2014-01-22T16:28:12.450Z"
|
||||
"metrics": {
|
||||
"rank":1
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
sampleTaskLogObject=
|
||||
{
|
||||
"_id":ObjectId("507f191e810c19729de860ea") #datestamp is built into objectId
|
||||
"calculatedBy":ObjectId("51eb2714fa058cb20d0006ef")
|
||||
"calculationTime":3201
|
||||
timedOut: false
|
||||
"sessions":[
|
||||
{
|
||||
"ID":ObjectId("52dfeb17c8b5f435c7000025")
|
||||
"metrics": {
|
||||
"rank":2
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID":ObjectId("51eb2714fa058cb20d00feda")
|
||||
"metrics": {
|
||||
"rank":1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
###
|
0
server/queues/sendwithus.coffee
Normal file
0
server/queues/sendwithus.coffee
Normal file
12
server/queues/task/ScoringTask.coffee
Normal file
12
server/queues/task/ScoringTask.coffee
Normal file
|
@ -0,0 +1,12 @@
|
|||
mongoose = require('mongoose')
|
||||
|
||||
ScoringTaskSchema = new mongoose.Schema(
|
||||
createdAt: {type: Date, expires: 3600} #expire document 1 hour after they are created
|
||||
calculator: {type:mongoose.Schema.Types.ObjectId}
|
||||
sentDate: {type: Number}
|
||||
messageIdentifierString: {type: String}
|
||||
calculationTimeMS: {type: Number, default: 0}
|
||||
sessions: {type: Array, default: []}
|
||||
)
|
||||
|
||||
module.exports = mongoose.model('scoringTask', ScoringTaskSchema)
|
50
server/routes/queue.coffee
Normal file
50
server/routes/queue.coffee
Normal file
|
@ -0,0 +1,50 @@
|
|||
log = require 'winston'
|
||||
errors = require '../commons/errors'
|
||||
scoringQueue = require '../queues/scoring'
|
||||
|
||||
|
||||
module.exports.setup = (app) ->
|
||||
scoringQueue.setup()
|
||||
|
||||
app.all '/queue/*', (req, res) ->
|
||||
setResponseHeaderToJSONContentType res
|
||||
|
||||
queueName = getQueueNameFromPath req.path
|
||||
try
|
||||
handler = loadQueueHandler queueName
|
||||
if isHTTPMethodGet req
|
||||
handler.dispatchTaskToConsumer req,res
|
||||
else if isHTTPMethodPut req
|
||||
handler.processTaskResult req,res
|
||||
else if isHTTPMethodPost req
|
||||
handler.createNewTask req, res #TODO: do not use this in production
|
||||
else
|
||||
sendMethodNotSupportedError req, res
|
||||
catch error
|
||||
log.error error
|
||||
sendQueueError req, res, error
|
||||
|
||||
setResponseHeaderToJSONContentType = (res) -> res.setHeader('Content-Type', 'application/json')
|
||||
|
||||
getQueueNameFromPath = (path) ->
|
||||
pathPrefix = '/queue/'
|
||||
pathAfterPrefix = path[pathPrefix.length..]
|
||||
partsOfURL = pathAfterPrefix.split '/'
|
||||
queueName = partsOfURL[0]
|
||||
queueName
|
||||
|
||||
loadQueueHandler = (queueName) -> require ('../queues/' + queueName)
|
||||
|
||||
|
||||
isHTTPMethodGet = (req) -> return req.route.method is 'get'
|
||||
|
||||
isHTTPMethodPost = (req) -> return req.route.method is 'post'
|
||||
|
||||
isHTTPMethodPut = (req) -> return req.route.method is 'put'
|
||||
|
||||
|
||||
sendMethodNotSupportedError = (req, res) -> errors.badMethod(res,"Queues do not support the HTTP method used." )
|
||||
|
||||
sendQueueError = (req,res, error) -> errors.serverError(res, "Route #{req.path} had a problem: #{error}")
|
||||
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
config = require '../server_config'
|
||||
sendwithusAPI = require 'sendwithus'
|
||||
swuAPIKey = config.mail.sendwithusAPIKey
|
||||
queues = require './commons/queue'
|
||||
|
||||
module.exports.setupRoutes = (app) ->
|
||||
return
|
||||
|
||||
|
||||
options = { DEBUG: not config.isProduction }
|
||||
module.exports.api = new sendwithusAPI swuAPIKey, options
|
||||
|
|
|
@ -31,6 +31,14 @@ config.mail.mailchimpAPIKey = process.env.COCO_MAILCHIMP_API_KEY || '';
|
|||
config.mail.mailchimpWebhook = process.env.COCO_MAILCHIMP_WEBHOOK || '/mail/webhook';
|
||||
config.mail.sendwithusAPIKey = process.env.COCO_SENDWITHUS_API_KEY || '';
|
||||
|
||||
config.queue = {};
|
||||
config.queue.accessKeyId = process.env.COCO_AWS_ACCESS_KEY_ID || '';
|
||||
config.queue.secretAccessKey = process.env.COCO_AWS_SECRET_ACCESS_KEY || '';
|
||||
config.queue.region = 'us-east-1';
|
||||
config.queue.simulationQueueName = "simulationQueue";
|
||||
config.mongoQueue = {};
|
||||
config.mongoQueue.queueDatabaseName = "coco_queue";
|
||||
|
||||
config.salt = process.env.COCO_SALT || 'pepper';
|
||||
config.cookie_secret = process.env.COCO_COOKIE_SECRET || 'chips ahoy';
|
||||
|
||||
|
|
|
@ -80,6 +80,7 @@ setupFacebookCrossDomainCommunicationRoute = (app) ->
|
|||
|
||||
exports.setupRoutes = (app) ->
|
||||
app.use app.router
|
||||
|
||||
baseRoute.setup app
|
||||
setupFacebookCrossDomainCommunicationRoute app
|
||||
setupFallbackRouteToIndex app
|
||||
|
|
Loading…
Add table
Reference in a new issue