This commit is contained in:
George Saines 2014-03-03 18:04:01 -08:00
commit 33e2571934
54 changed files with 1111 additions and 728 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View file

@ -19,7 +19,7 @@ module.exports = class CocoRouter extends Backbone.Router
'editor/:model(/:slug_or_id)(/:subview)': 'editorModelView'
# Experimenting with direct links
'play/ladder/:levelID/team/:team': go('play/ladder/team_view')
# 'play/ladder/:levelID/team/:team': go('play/ladder/team_view')
# db and file urls call the server directly
'db/*path': 'routeToServer'

View file

@ -42,6 +42,7 @@ module.exports = class Camera extends CocoClass
'level:restarted': 'onLevelRestarted'
'sprite:mouse-down': 'onMouseDown'
'sprite:dragged': 'onMouseDragged'
'camera-zoom-to': 'onZoomTo'
# TODO: Fix tests to not use mainLayer
constructor: (@canvasWidth, @canvasHeight, angle=Math.asin(0.75), hFOV=d2r(30)) ->
@ -169,7 +170,7 @@ module.exports = class Camera extends CocoClass
onMouseDown: (e) ->
return if @dragDisabled
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
onMouseDragged: (e) ->
return if @dragDisabled
target = @boundTarget(@target, @zoom)
@ -180,7 +181,7 @@ module.exports = class Camera extends CocoClass
@zoomTo newPos, @zoom, 0
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
Backbone.Mediator.publish 'camera:dragged'
onLevelRestarted: ->
@setBounds(@firstBounds, false)
@ -220,7 +221,7 @@ module.exports = class Camera extends CocoClass
newTarget ?= {x:0, y:0}
newTarget = (@newTarget or @target) if @locked
newZoom = Math.min((Math.max @minZoom, newZoom), MAX_ZOOM)
thangType = @target?.sprite?.thangType
if thangType
@offset = _.clone(thangType.get('positions')?.torso or {x: 0, y:0})
@ -229,7 +230,7 @@ module.exports = class Camera extends CocoClass
@offset.y *= scale
else
@offset = {x: 0, y:0}
return if @zoom is newZoom and newTarget is newTarget.x and newTarget.y is newTarget.y
@finishTween(true)
@ -247,7 +248,7 @@ module.exports = class Camera extends CocoClass
@target = newTarget
@zoom = newZoom
@updateZoom true
focusedOnSprite: ->
return @target?.name
@ -308,3 +309,6 @@ module.exports = class Camera extends CocoClass
createjs.Tween.removeTweens @
@finishTween = null
super()
onZoomTo: (pos, time) ->
@zoomTo(@worldToSurface(pos), @zoom, time)

View file

@ -22,6 +22,12 @@ module.exports = class WizardSprite extends IndieSprite
'echo-self-wizard-sprite': 'onEchoSelfWizardSprite'
'echo-all-wizard-sprites': 'onEchoAllWizardSprites'
shortcuts:
'up': 'onMoveKey'
'down': 'onMoveKey'
'left': 'onMoveKey'
'right': 'onMoveKey'
constructor: (thangType, options) ->
if options?.isSelf
options.colorConfig = _.cloneDeep(me.get('wizard')?.colorConfig) or {}
@ -102,7 +108,7 @@ module.exports = class WizardSprite extends IndieSprite
defaultPos: -> x: 35, y: 24, z: @thang.depth / 2 + @thang.bobHeight
move: (pos, duration) -> @setTarget(pos, duration)
setTarget: (newTarget, duration) ->
setTarget: (newTarget, duration, isLinear=false) ->
# ignore targets you're already heading for
targetPos = @getPosFromTarget(newTarget)
return if @targetPos and @targetPos.x is targetPos.x and @targetPos.y is targetPos.y
@ -115,7 +121,7 @@ module.exports = class WizardSprite extends IndieSprite
@shoveOtherWizards(true) if @targetSprite
@targetSprite = if isSprite then newTarget else null
@targetPos = targetPos
@beginMoveTween(duration)
@beginMoveTween(duration, isLinear)
@shoveOtherWizards()
Backbone.Mediator.publish('self-wizard:target-changed', {sender:@}) if @isSelf
@ -127,7 +133,7 @@ module.exports = class WizardSprite extends IndieSprite
return target if target.x?
return target.thang.pos
beginMoveTween: (duration=1000) ->
beginMoveTween: (duration=1000, isLinear=false) ->
# clear the old tween
createjs.Tween.removeTweens(@)
@ -140,8 +146,11 @@ module.exports = class WizardSprite extends IndieSprite
@updatePosition()
@endMoveTween()
return
if isLinear
ease = createjs.Ease.linear
else
ease = createjs.Ease.getPowInOut(3.0)
ease = createjs.Ease.getPowInOut(3.0)
createjs.Tween
.get(@)
.to({tweenPercentage:0.0}, duration, ease)
@ -225,3 +234,22 @@ module.exports = class WizardSprite extends IndieSprite
updateMarks: ->
super() if @displayObject.visible # not if we hid the wiz
onMoveKey: (e) ->
return unless @isSelf
e?.preventDefault()
yMovement = 0
xMovement = 0
yMovement += 2 if key.isPressed('up')
yMovement -= 2 if key.isPressed('down')
xMovement += 2 if key.isPressed('right')
xMovement -= 2 if key.isPressed('left')
@moveWizard xMovement, yMovement
moveWizard: (x, y) ->
interval = 500
position = {x: @targetPos.x + x, y: @targetPos.y + y}
@setTarget(position, interval, true)
@updatePosition()
Backbone.Mediator.publish 'camera-zoom-to', position, interval

View file

@ -44,6 +44,7 @@ module.exports.thangNames = thangNames =
"Huburt"
"Sterling"
"Alistair"
"Cid"
"Remy"
"Stormy"
"Halle"

View file

@ -130,6 +130,7 @@ module.exports = nativeDescription: "English", englishDescription: "English", tr
new_password_verify: "Verify"
email_subscriptions: "Email Subscriptions"
email_announcements: "Announcements"
email_notifications: "Notifications"
email_notifications_description: "Get periodic notifications for your account."
email_announcements_description: "Get emails on the latest news and developments at CodeCombat."
contributor_emails: "Contributor Class Emails"
@ -246,6 +247,12 @@ module.exports = nativeDescription: "English", englishDescription: "English", tr
create_system_title: "Create New System"
new_component_title: "Create New Component"
new_component_field_system: "System"
new_article_title: "Create a New Article"
new_thang_title: "Create a New Thang Type"
new_level_title: "Create a New Level"
article_search_title: "Search Articles Here"
thang_search_title: "Search Thang Types Here"
level_search_title: "Search Levels Here"
article:
edit_btn_preview: "Preview"

View file

@ -8,7 +8,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
delay_1_sec: "1 seconde"
delay_3_sec: "3 secondes"
delay_5_sec: "5 secondes"
manual: "Handboek"
manual: "Handmatig"
fork: "Fork"
play: "Spelen"
@ -116,7 +116,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
account_settings:
title: "Account Instellingen"
not_logged_in: "Log in of maak een account om je instellingen aan te passen."
autosave: "Aanpassingen Worden Automatisch Opgeslagen"
autosave: "Aanpassingen Automatisch Opgeslagen"
me_tab: "Ik"
picture_tab: "Afbeelding"
wizard_tab: "Tovenaar"
@ -130,6 +130,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
new_password_verify: "Verifieer"
email_subscriptions: "E-mail Abonnementen"
email_announcements: "Aankondigingen"
email_notifications: "Notificaties"
email_notifications_description: "Krijg periodieke meldingen voor jouw account."
email_announcements_description: "Verkrijg emails over het laatste nieuws en de ontwikkelingen bij CodeCombat."
contributor_emails: "Medewerker Klasse emails"
@ -215,11 +216,11 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
main_title: "CodeCombat Editors"
main_description: "Maak je eigen levels, campagnes, eenheden en leermateriaal. Wij bieden alle programma's aan die u nodig heeft!"
article_title: "Artikel Editor"
article_description: "Schrijf artikels dat spelers een overzicht geven over programmeer concepten die kunnen gebruikt worden over een variëteit van levels en campagnes."
article_description: "Schrijf artikels die spelers een overzicht geven over programmeer concepten die kunnen gebruikt worden over een variëteit van levels en campagnes."
thang_title: "Thang Editor"
thang_description: "Maak eenheden, beschrijf hun default logica, graphics en audio. Momenteel is enkel het importeren van vector graphics geëxporteerd in Flash ondersteund."
thang_description: "Maak eenheden, beschrijf hun standaard logica, graphics en audio. Momenteel is enkel het importeren van vector graphics geëxporteerd in Flash ondersteund."
level_title: "Level Editor"
level_description: "Bevat het programma om te programmeren, audio te uploaden en aangepaste logica om alle soorten levels te maken. Het is alles wat wijzelf ook gebruiken!"
level_description: "Bevat het programma om te programmeren, audio te uploaden en aangepaste logica te creëren om alle soorten levels te maken. Het is alles wat wijzelf ook gebruiken!"
security_notice: "Veel belangrijke elementen in deze editors zijn momenteel niet actief. Met dat wij de veiligheid van deze systemen verbeteren, zullen ook deze elementen beschikbaar worden. Indien u deze elementen al eerder wil gebruiken, "
contact_us: "contacteer ons!"
hipchat_prefix: "Je kan ons ook vinden in ons"
@ -246,6 +247,12 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
create_system_title: "Maak een nieuw Systeem aan"
new_component_title: "Maak een nieuw Component aan"
new_component_field_system: "Systeem"
new_article_title: "Maak een Nieuw Artikel"
new_thang_title: "Maak een Nieuw Thang Type"
new_level_title: "Maak een Nieuw Level"
article_search_title: "Zoek Artikels Hier"
thang_search_title: "Zoek Thang Types Hier"
level_search_title: "Zoek Levels Hier"
article:
edit_btn_preview: "Voorbeeld"

View file

@ -201,15 +201,15 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
hud_continue: "Продолжить (нажмите Shift+Пробел)"
spell_saved: "Заклинание сохранено"
# 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: "Админ панель"
av_entities_sub_title: "Сущности"
av_entities_users_url: "Пользователи"
av_entities_active_instances_url: "Активные экземпляры"
av_other_sub_title: "Другое"
av_other_debug_base_url: "База (для отладки base.jade)"
u_title: "Список пользователей"
lg_title: "Последние игры"
editor:
main_title: "Редакторы CodeCombat"
@ -224,7 +224,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
contact_us: "свяжитесь с нами!"
hipchat_prefix: "Также вы можете найти нас в нашей"
hipchat_url: "комнате HipChat."
# level_some_options: "Some Options?"
level_some_options: "Ещё опции"
level_tab_thangs: "Объекты"
level_tab_scripts: "Скрипты"
level_tab_settings: "Настройки"
@ -254,7 +254,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
general:
and: "и"
name: "Имя"
# body: "Body"
body: "Содержание"
version: "Версия"
commit_msg: "Сопроводительное сообщение"
version_history_for: "История версий для: "
@ -418,22 +418,22 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
diplomat_join_suf_github: ", отредактируйте его онлайн и отправьте запрос на включение изменений. Кроме того, установите флажок ниже, чтобы быть в курсе новых разработок интернационализации!"
more_about_diplomat: "Узнать больше о том, как стать Дипломатом"
diplomat_subscribe_desc: "Получать email-ы о i18n разработках и уровнях для перевода."
# ambassador_summary: "Мы пытаемся создать сообщество, и каждое сообщество нуждается в службе поддержки, когда есть проблемы. У нас есть чаты, электронная почта и социальные сети, чтобы наши пользователи могли познакомиться с игрой. Если вы хотите помочь людям втянуться, получать удовольствие и учиться программированию, этот класс для вас." # Not done yet
# ambassador_introduction: "This is a community we're building, and you are the connections. We've got Olark chats, emails, and social networks with lots of people to talk with and help get acquainted with the game and learn from. If you want to help people get involved and have fun, and get a good feel of the pulse of CodeCombat and where we're going, then this class might be for you."
# ambassador_attribute_1: "Communication skills. Be able to identify the problems players are having and help them solve them. Also, keep the rest of us informed about what players are saying, what they like and don't like and want more of!"
# ambassador_join_desc: "tell us a little about yourself, what you've done and what you'd be interested in doing. We'll go from there!"
# ambassador_join_note_strong: "Note"
# ambassador_join_note_desc: "One of our top priorities is to build multiplayer where players having difficulty solving levels can summon higher level wizards to help them. This will be a great way for ambassadors to do their thing. We'll keep you posted!"
# more_about_ambassador: "Узнать больше о том, как стать Ambassador" # Not done yet
# ambassador_subscribe_desc: "Get emails on support updates and multiplayer developments."
# counselor_summary: "None of the above roles fit what you are interested in? Do not worry, we are on the lookout for anybody who wants a hand in the development of CodeCombat! If you are interested in teaching, game development, open source management, or anything else that you think will be relevant to us, then this class is for you."
# counselor_introduction_1: "Do you have life experience? A different perspective on things that can help us decide how to shape CodeCombat? Of all these roles, this will probably take the least time, but individually you may make the most difference. We're on the lookout for wisened sages, particularly in areas like: teaching, game development, open source project management, technical recruiting, entrepreneurship, or design."
# counselor_introduction_2: "Or really anything that is relevant to the development of CodeCombat. If you have knowledge and want to share it to help grow this project, then this class might be for you."
# counselor_attribute_1: "Experience, in any of the areas above or something you think might be helpful."
# counselor_attribute_2: "A little bit of free time!"
# counselor_join_desc: "tell us a little about yourself, what you've done and what you'd be interested in doing. We'll put you in our contact list and be in touch when we could use advice (not too often)."
# more_about_counselor: "Узнать больше о том, как стать Counselor" # Not done yet
# changes_auto_save: "Changes are saved automatically when you toggle checkboxes."
ambassador_summary: "Мы пытаемся создать сообщество, и каждое сообщество нуждается в службе поддержки, когда есть проблемы. У нас есть чаты, электронная почта и социальные сети, чтобы наши пользователи могли познакомиться с игрой. Если вы хотите помочь людям втянуться, получать удовольствие и учиться программированию, этот класс для вас."
ambassador_introduction: "Это сообщество, которое мы создаём, и вы соединяете. У нас есть Olark чаты, электронная почта и социальные сети с уймой людей, с которыми нужно поговорить, помочь в ознакомлении с игрой и обучении из неё. Если вы хотите помочь людям втянуться, получать удовольствие, наслаждаться и и куда мы идём, этот класс для вас."
ambassador_attribute_1: "Навыки общения. Уметь определять проблемы игроков и помогать решить их. Кроме того, держите всех нас в курсе о том, что игроки говорят, что им нравится, не нравится и чего хотят больше!"
ambassador_join_desc: "расскажите нам немного о себе, чем вы занимались и чем хотели бы заниматься. Отсюда и начнём!"
ambassador_join_note_strong: "Примечание"
ambassador_join_note_desc: "Одним из наших главных приоритетов является создание мультиплеера, где игроки столкнутся с труднорешаемыми уровнями и могут призвать более высокоуровневых волшебников для помощи. Это будет отличным способом для послов делать свое дело. Мы будем держать вас в курсе!"
more_about_ambassador: "Узнать больше о том, как стать Послом"
ambassador_subscribe_desc: "Получать email-ы о разработке мультиплеера и обновлениях в системе поддержки."
counselor_summary: "Ни одна из вышеупомянутых ролей не соответствует тому, в чём вы заинтересованы? Не волнуйтесь, мы в поисках тех, кто хочет приложить руку к разработке CodeCombat! Если вы заинтересованы в обучении, разработке игр, управлением проектами с открытым исходным кодом, или в чём-нибудь ещё, что, как вы думаете, будет актуально для нас, то этот класс для вас."
counselor_introduction_1: "У вас есть жизненный опыт? Другая точка зрения на вещи, которые могут помочь нам решить, как формировать CodeCombat? Из всех этих ролей, эта, возможно, займёт меньше всего времени, но по отдельности, вы можете сделать наибольшие изменения. Мы в поисках морщинистых мудрецов, особенно в таких областях, как: обучение, разработка игр, управление проектами с открытым исходным кодом, технической рекрутинг, предпринимательство или дизайн."
counselor_introduction_2: "Или действительно всё, что имеет отношение к развитию CodeCombat. Если у вас есть знания и вы хотите поделиться ими, чтобы помочь вырастить этот проект, то этот класс для вас."
counselor_attribute_1: "Опыт, в любой из областей выше, или в том, что, как вы думаете, может быть полезным."
counselor_attribute_2: "Немного свободного времени!"
counselor_join_desc: "расскажите нам немного о себе, чем вы занимались и чем хотели бы заниматься. Мы поместим вас в наш список контактов и выйдем на связь, когда нам понадобится совет(не слишком часто)."
more_about_counselor: "Узнать больше о том, как стать Советником"
changes_auto_save: "Изменения сохраняются автоматически при переключении флажков."
diligent_scribes: "Наши старательные Писари:"
powerful_archmages: "Наши могущественные Архимаги:"
creative_artisans: "Наши творческие Ремесленники:"

View file

@ -117,7 +117,6 @@ module.exports = class Level extends CocoModel
else if path is '/'
# We also we need to make sure we grab the Wizard ThangType and the Marks. Hackitrooooid!
for [type, original] in [
["Wizard", "52a00d55cf1818f2be00000b"]
["Highlight", "529f8fdbdacd325127000003"]
["Selection", "52aa5f7520fccb0000000002"]
["Target", "52b32ad97385ec3d03000001"]
@ -126,4 +125,5 @@ module.exports = class Level extends CocoModel
link = "/db/thang_type/#{original}/version"
model = CocoModel.getOrMakeModelFromLink link, shouldLoadProjection
models.push model if model
models.push ThangType.loadUniversalWizard()
models

View file

@ -172,7 +172,10 @@ module.exports = class ThangType extends CocoModel
key = spriteOptionsOrKey
key = if _.isString(key) then key else @spriteSheetKey(@fillOptions(key))
spriteSheet = @spriteSheets[key]
spriteSheet ?= @buildSpriteSheet({portraitOnly:true})
if not spriteSheet
options = if _.isPlainObject spriteOptionsOrKey then spriteOptionsOrKey else {}
options.portraitOnly = true
spriteSheet = @buildSpriteSheet(options)
return unless spriteSheet
canvas = $("<canvas width='#{size}' height='#{size}'></canvas>")
stage = new createjs.Stage(canvas[0])
@ -210,3 +213,12 @@ module.exports = class ThangType extends CocoModel
onFileUploaded: =>
console.log 'Image uploaded'
@loadUniversalWizard: ->
return @wizardType if @wizardType
wizOriginal = "52a00d55cf1818f2be00000b"
url = "/db/thang_type/#{wizOriginal}/version"
@wizardType = new module.exports()
@wizardType.url = -> url
@wizardType.fetch()
@wizardType

View file

@ -0,0 +1,9 @@
@import "bootstrap/mixins"
@import "bootstrap/variables"
#not-found-view
.not-found-image
display: block
margin-left: auto
margin-right: auto

View file

@ -1,4 +1,10 @@
#ladder-view
h1
text-align: center
.tab-pane
margin-top: 10px
.score-cell
width: 50px
@ -6,14 +12,6 @@
margin-bottom: 10px
background-image: none
.intro-button
width: 45%
margin: 0 2.5%
#simulation-status-text
display: inline
margin-left: 10px
.name-col-cell
max-width: 300px
text-overflow: ellipsis

View file

@ -0,0 +1,141 @@
#ladder-play-modal
.tutorial-suggestion
text-align: center
font-size: 18px
.play-option
margin-bottom: 15px
width: 100%
height: 100px
overflow: hidden
background: white
border: 1px solid #333
position: relative
-webkit-transition: opacity 0.3s ease-in-out
-moz-transition: opacity 0.3s ease-in-out
-ms-transition: opacity 0.3s ease-in-out
-o-transition: opacity 0.3s ease-in-out
transition: opacity 0.3s ease-in-out
opacity: 0.4
border-radius: 5px
.only-one
-webkit-transition: opacity 0.3s ease-in-out
-moz-transition: opacity 0.3s ease-in-out
-ms-transition: opacity 0.3s ease-in-out
-o-transition: opacity 0.3s ease-in-out
transition: opacity 0.3s ease-in-out
opacity: 0
.play-option:hover
opacity: 1
.only-one
opacity: 1
.my-icon
position: relative
left: 0
top: -10px
z-index: 1
.my-team-icon
height: 60px
position: relative
top: -10px
left: 10px
z-index: 0
.opponent-team-icon
height: 60px
position: relative
top: 10px
right: 10px
z-index: 0
float: right
-moz-transform: scaleX(-1)
-o-transform: scaleX(-1)
-webkit-transform: scaleX(-1)
transform: scaleX(-1)
filter: FlipH
-ms-filter: "FlipH"
.opponent-icon
position: relative
float: right
right: 0
top: -10px
-moz-transform: scaleX(-1)
-o-transform: scaleX(-1)
-webkit-transform: scaleX(-1)
transform: scaleX(-1)
filter: FlipH
-ms-filter: "FlipH"
z-index: 1
.name-label
border-bottom: 20px solid lightslategray
height: 0
width: 40%
position: absolute
bottom: 0
color: black
font-weight: bold
text-align: center
z-index: 2
span
position: relative
top: 1px
.my-name
border-right: 15px solid transparent
left: 0
span
left: 3px
.opponent-name
border-left: 15px solid transparent
right: 0
//text-align: right
span
right: 3px
.difficulty
border-top: 25px solid darkgray
border-left: 20px solid transparent
border-right: 20px solid transparent
height: 0
width: 30%
position: absolute
left: 35%
top: 0
color: black
text-align: center
font-size: 18px
font-weight: bold
span
position: relative
top: -25px
.easy-option .difficulty
border-top: 25px solid limegreen
.medium-option .difficulty
border-top: 25px solid darkorange
.hard-option .difficulty
border-top: 25px solid black
color: white
.vs
position: absolute
left: 40%
right: 40%
text-align: center
top: 35px
font-size: 40px
font-weight: bolder
color: black

View file

@ -1,12 +0,0 @@
#ladder-team-view
#rank-button
margin-top: 15px
#competitors-column .well
font-size: 18px
padding: 7px
#your-score
margin-top: 20px
text-align: center
font-size: 20px

View file

@ -1,25 +1,35 @@
#level-victory-modal
.victory-banner
float: right
width: 150px
position: relative
.modal-footer
clear: both
padding-top: 15px
p.sign-up-poke
text-align: left
margin-bottom: 10px
.sign-up-button
margin-left: 20px
.next-level-button
margin-bottom: 10px
margin-right: 30px
float: right
.sign-up-button
float: right
margin-left: 10px
.next-level-button
float: right
margin-left: 10px
.rating
float: center
margin-right: 22%
float: left
position: relative
top: 5px
span
margin-right: 5px
i
cursor: pointer
padding: 2px
.review
margin-top: 5px
width: 100%
@ -31,23 +41,10 @@
width: 100%
height: 80px
box-sizing: border-box
.share-buttons
padding-top: 15px
clear: both
.modal-header
text-align: left
.victory-banner
width: 450px
position: absolute
left: -150px
.modal-dialog
margin-left: 30%
padding-left: 300px
width: 700px
.modal-footer
margin: 0px
padding: 0px
text-align: center

View file

@ -8,7 +8,7 @@ block content
p(data-i18n="account_settings.not_logged_in") Log in or create an account to change your settings.
else
button.btn#save-button.disabled.secret(data-i18n="account_settings.saveBackups") Changes Save Automatically
button.btn#save-button.disabled.secret(data-i18n="account_settings.autosave") Changes Save Automatically
ul.nav.nav-pills#settings-tabs
li

View file

@ -86,7 +86,7 @@ block content
li Slovak - Anon
li Persian - Reza Habibi (Rehb)
li Czech - vanous
li Russian - fess89, ser-storchak
li Russian - fess89, ser-storchak, Mr A
li Ukrainian - fess89
li Italian - flauta
li Norwegian - bardeh

View file

@ -10,8 +10,9 @@ block content
if me.get('anonymous')
a.btn.btn-primary.open-modal-button(data-toggle="coco-modal", data-target="modal/signup", role="button") Sign Up to Create a New #{modelLabel}
else
a.btn.btn-primary.open-modal-button(href='#new-model-modal', role="button", data-toggle="modal") Create a New #{modelLabel}
input#search(placeholder="Search #{modelLabel}s Here")
a.btn.btn-primary.open-modal-button(href='#new-model-modal', role="button", data-toggle="modal" data-i18n="#{currentNew}") Create a New Something
input#search(data-i18n="[placeholder]#{currentSearch}")
hr
div.results
table

View file

@ -3,3 +3,8 @@ extends /templates/base
block content
h1.text-center(data-i18n="not_found.page_not_found") Page Not Found
num = Math.floor(Math.random() * 3) + 1
img(src="/images/pages/not_found/404_#{num}.png" class="not-found-image")

View file

@ -2,17 +2,11 @@ extends /templates/base
block content
div#level-column
h3= level.get('name')
div#level-description
!{description}
if !me.get('anonymous')
//a(href="http://www.youtube.com/watch?v=IFvfZiJGDsw&list=HL1392928835&feature=mh_lolz").intro-button.btn.btn-primary.btn-lg Watch the Video
a(href="/play/level/brawlwood-tutorial").intro-button.btn.btn-primary.btn-lg Play the Tutorial
if levelDescription
div!= levelDescription
else
h1= level.get('name')
hr
if me.get('anonymous')
div#must-log-in
p
@ -23,41 +17,37 @@ block content
else
div#columns.row
div.column.col-md-2
for team in teams
div.column.col-md-6
a(href="/play/ladder/#{levelID}/team/#{team.id}", style="background-color: #{team.primaryColor}").play-button.btn.btn-danger.btn-block.btn-lg
div.column.col-md-4
a(style="background-color: #{team.primaryColor}", data-team=team.id).play-button.btn.btn-danger.btn-block.btn-lg
span Play As
span= team.name
table.table.table-bordered.table-condensed.table-hover
//(style="background-color: #{team.bgColor}")
tr
th(colspan=3, style="color: #{team.primaryColor}")
span= team.name
span Leaderboard
tr
th Score
th.name-col-cell Name
th
for session in team.leaderboard.topPlayers.models
- var myRow = session.get('creator') == me.id
tr(class=myRow ? "success" : "")
td.score-cell= session.get('totalScore').toFixed(2)
td.name-col-cell= session.get('creatorName') || "Anonymous"
td
if(!myRow)
a(href="/play/level/#{level.get('slug') || level.id}/?team=#{team.otherTeam}&opponent=#{session.id}") Battle as #{team.otherTeam}!
else
a(href="/play/ladder/#{levelID}/team/#{team.id}") View your #{team.id} matches.
unless me.attributes.anonymous
hr
button.btn.btn-warning.btn-lg.highlight#simulate-button(style="margin-bottom:10px;") Simulate Games!
p(id="simulation-status-text", style="display:inline; margin-left:10px;")
if simulationStatus
| #{simulationStatus}
else
| By simulating games you can get your game ranked faster!
if me.isAdmin()
button.btn.btn-danger.btn-lg.highlight#simulate-all-button(style="margin-bottom:10px; float: right;") RESET AND SIMULATE GAMES
div.column.col-md-2
hr
ul.nav.nav-pills
li.active
a(href="#ladder", data-toggle="tab") Ladder
li
a(href="#my-matches", data-toggle="tab") My Matches
li
a(href="#simulate", data-toggle="tab") Simulate
div.tab-content
.tab-pane.active.well#ladder
#ladder-tab-view
.tab-pane.well#my-matches
#my-matches-tab-view
.tab-pane.well#simulate
p(id="simulation-status-text")
if simulationStatus
| #{simulationStatus}
else
| By simulating games you can get your game ranked faster!
p
button.btn.btn-warning.btn-lg.highlight#simulate-button() Simulate Games!
if me.isAdmin()
p
button.btn.btn-danger.btn-lg.highlight#simulate-all-button() RESET AND SIMULATE GAMES

View file

@ -0,0 +1,21 @@
div#columns.row
for team in teams
div.column.col-md-6
table.table.table-bordered.table-condensed.table-hover
tr
th(colspan=3, style="color: #{team.primaryColor}")
span= team.name
span Leaderboard
tr
th Score
th.name-col-cell Name
th
for session in team.leaderboard.topPlayers.models
- var myRow = session.get('creator') == me.id
tr(class=myRow ? "success" : "")
td.score-cell= Math.round(session.get('totalScore') * 100)
td.name-col-cell= session.get('creatorName') || "Anonymous"
td
a(href="/play/level/#{level.get('slug') || level.id}/?team=#{team.otherTeam}&opponent=#{session.id}")
span Battle as #{team.otherTeam}!

View file

@ -0,0 +1,53 @@
//if matches.length
// p#your-score
// span Your Current Score:
// span
// strong= score
div#columns.row
for team in teams
div.matches-column.col-md-6
table.table.table-bordered.table-condensed
tr
th(colspan=4, style="color: #{team.primaryColor}")
span Your
span
span= team.name
span
span Matches
if team.session
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.failed.hidden Failed to Rank
tr
th Result
th Opponent
th When
th
for match in team.matches
tr
td.state-cell
if match.state === 'win'
span.win Win
if match.state === 'loss'
span.loss Loss
if match.state === 'tie'
span.tie Tie
td.name-cell= match.opponentName || "Anonymous"
td.time-cell= match.when
td.battle-cell
- var text = match.state === 'win' ? 'Watch your victory' : 'Defeat the ' + team.otherTeam
a(href="/play/level/#{levelID}?team=#{team.id}&opponent=#{match.sessionID}")= text
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.

View file

@ -0,0 +1,72 @@
extends /templates/modal/modal_base
block modal-header-content
h3 Choose an Opponent
block modal-body-content
p.tutorial-suggestion
span Not sure what's going on?
|
a(href="/play/level/brawlwood-tutorial") Play the tutorial first.
a(href="/play/level/#{levelID}?team=#{teamID}")
div.play-option
img(src=myPortrait).my-icon.only-one
img(src="/images/pages/play/ladder/"+teamID+"_ladder_tutorial.png", style="border: 1px solid #{teamColor}; background: #{teamBackgroundColor}").my-team-icon.img-circle.only-one
img(src=genericPortrait).opponent-icon
img(src="/images/pages/play/ladder/"+otherTeamID+"_ladder_tutorial.png", style="border: 1px solid #{opponentTeamColor}; background: #{opponentTeamBackgroundColor}").opponent-team-icon.img-circle
div.my-name.name-label.only-one
span= myName
div.opponent-name.name-label
span Simple AI
div.difficulty
span Warmup
div.vs VS
if challengers.easy
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.easy.sessionID}")
div.play-option.easy-option
img(src=myPortrait).my-icon.only-one
img(src="/images/pages/play/ladder/"+teamID+"_ladder_easy.png", style="border: 1px solid #{teamColor}; background: #{teamBackgroundColor}").my-team-icon.img-circle.only-one
img(src=challengers.easy.opponentImageSource||genericPortrait).opponent-icon
img(src="/images/pages/play/ladder/"+otherTeamID+"_ladder_easy.png", style="border: 1px solid #{opponentTeamColor}; background: #{opponentTeamBackgroundColor}").opponent-team-icon.img-circle
div.my-name.name-label.only-one
span= myName
div.opponent-name.name-label
span= challengers.easy.opponentName
div.difficulty
span Easy
div.vs VS
if challengers.medium
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.medium.sessionID}")
div.play-option.medium-option
img(src=myPortrait).my-icon.only-one
img(src="/images/pages/play/ladder/"+teamID+"_ladder_medium.png", style="border: 1px solid #{teamColor}; background: #{teamBackgroundColor}").my-team-icon.img-circle.only-one
img(src=challengers.medium.opponentImageSource||genericPortrait).opponent-icon
img(src="/images/pages/play/ladder/"+otherTeamID+"_ladder_medium.png", style="border: 1px solid #{opponentTeamColor}; background: #{opponentTeamBackgroundColor}").opponent-team-icon.img-circle
div.my-name.name-label.only-one
span= myName
div.opponent-name.name-label
span= challengers.medium.opponentName
div.difficulty
span Medium
div.vs VS
if challengers.hard
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.hard.sessionID}")
div.play-option.hard-option
img(src=myPortrait).my-icon.only-one
img(src="/images/pages/play/ladder/"+teamID+"_ladder_hard.png", style="border: 1px solid #{teamColor}; background: #{teamBackgroundColor}").my-team-icon.img-circle.only-one
img(src=challengers.hard.opponentImageSource||genericPortrait).opponent-icon
img(src="/images/pages/play/ladder/"+otherTeamID+"_ladder_hard.png", style="border: 1px solid #{opponentTeamColor}; background: #{opponentTeamBackgroundColor}").opponent-team-icon.img-circle
div.my-name.name-label.only-one
span= myName
div.opponent-name.name-label
span= challengers.hard.opponentName
div.difficulty
span Hard
div.vs VS
block modal-footer

View file

@ -1,112 +0,0 @@
extends /templates/base
block content
ol.breadcrumb
li
a(href="/") Home
li
a(href="/play/ladder/#{levelID}")= level.get('name')
li.active= teamName
p
| In this level, you play against everyone who has ever written strategies for the opposing forces.
| Choose from the suggested players on the right, playing as many and as long as you like,
| and when you are ready to test your grand strategy against the whole ladder, return and click the rank button.
p
| After your first submission, your code will also continuously run against other players as they rank themselves.
if matches.length
p#your-score
span Your Current Score:
span
strong= score
div#columns.row
div#matches-column.col-md-6
h3.pull-left Ranked Games
button.btn.btn-warning.pull-right#rank-button
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.failed.hidden Failed to Rank
hr.clearfix(style="clear: both")
if matches.length
table.table.table-bordered.table-condensed
tr
th Result
th Opponent
th When
for match in matches
tr
td.state-cell
if match.state === 'win'
span.win Win
if match.state === 'loss'
span.loss Loss
if match.state === 'tie'
span.tie Tie
td.name-cell= match.opponentName || "Anonymous"
td.time-cell= match.when
td.battle-cell
- var text = match.state === 'win' ? 'Watch your victory' : 'Defeat the ' + otherTeamID
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{match.sessionID}")= text
else
div.alert.alert-warning
| No ranked matches played yet!
| Play some competitors on the right and then come back to get your game ranked.
// finish this once matches are available
div#competitors-column.col-md-6
h3 Your Competitors
.well.text-muted
div.row
div.col-md-2
span.warmup Warmup
span :
div.col-md-10
a(href="/play/level/#{levelID}?team=#{teamID}")
span.warmup Play #{teamID} vs Default #{otherTeamID}
if challengers.easy
.well
div.row.text-info.bg-info
div.col-md-2
span.easy Easy
span :
div.col-md-10
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.easy.sessionID}")
span Play #{teamID} vs
strong= challengers.easy.opponentName
span #{otherTeamID}
if challengers.medium
.well
div.row.text-warning.bg-warning
div.col-md-2
span.medium Medium
span :
div.col-md-10
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.medium.sessionID}")
span Play #{teamID} vs
strong= challengers.medium.opponentName
span #{otherTeamID}
if challengers.hard
.well
div.row.text-danger.bg-danger
div.col-md-2
span.hard Hard
span :
div.col-md-10
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.hard.sessionID}")
span Play #{teamID} vs
strong= challengers.hard.opponentName
span #{otherTeamID}

View file

@ -7,5 +7,6 @@
if hasBar
span.prop-value.bar-prop
.bar
span.prop-value.bar-prop-value
else
span.prop-value

View file

@ -2,8 +2,6 @@
.modal-dialog
img.victory-banner(src="/images/level/victory.png", alt="")
.modal-header
button(type='button', data-dismiss="modal", aria-hidden="true").close &times;
h3
@ -11,7 +9,9 @@
span= levelName
span(data-i18n="play_level.victory_title_suffix") Complete
.modal-body!= body
.modal-body
img.victory-banner(src="/images/level/victory.png", alt="")
div!= body
.modal-footer
if hasNextLevel

View file

@ -39,6 +39,7 @@ class LiveEditingMarkup extends TreemaNode.nodeMap.ace
filename: InkBlob.filename
mimetype: InkBlob.mimetype
path: @settings.filePath
force: true
@uploadingPath = [@settings.filePath, InkBlob.filename].join('/')
$.ajax('/file', { type: 'POST', data: body, success: @onFileUploaded })
@ -147,9 +148,8 @@ class SoundFileTreema extends TreemaNode.nodeMap.string
filename: InkBlob.filename
mimetype: InkBlob.mimetype
path: @settings.filePath
force: true
# Automatically overwrite if the same path was put in here before
body.force = true # if InkBlob.filename is @data
@uploadingPath = [@settings.filePath, InkBlob.filename].join('/')
$.ajax('/file', { type: 'POST', data: body, success: @onFileUploaded })
@ -185,9 +185,8 @@ class ImageFileTreema extends TreemaNode.nodeMap.string
filename: InkBlob.filename
mimetype: InkBlob.mimetype
path: @settings.filePath
force: true
# Automatically overwrite if the same path was put in here before
body.force = true # if InkBlob.filename is @data
@uploadingPath = [@settings.filePath, InkBlob.filename].join('/')
$.ajax('/file', { type: 'POST', data: body, success: @onFileUploaded })

View file

@ -11,6 +11,10 @@ module.exports = class HomeView extends View
events:
'mouseover #beginner-campaign': 'onMouseOverButton'
'mouseout #beginner-campaign': 'onMouseOutButton'
constructor: ->
super(arguments...)
ThangType.loadUniversalWizard()
getRenderData: ->
c = super()
@ -28,12 +32,8 @@ module.exports = class HomeView extends View
@$el.find('.modal').on 'shown.bs.modal', ->
$('input:visible:first', @).focus()
wizOriginal = "52a00d55cf1818f2be00000b"
url = "/db/thang_type/#{wizOriginal}/version"
@wizardType = new ThangType()
@wizardType.url = -> url
@wizardType.fetch()
@wizardType.once 'sync', @initCanvas
@wizardType = ThangType.wizardType
if @wizardType.loaded then @initCanvas else @wizardType.once 'sync', @initCanvas, @
# Try to find latest level and set "Play" link to go to that level
if localStorage?
@ -48,7 +48,7 @@ module.exports = class HomeView extends View
else
console.log("TODO: Insert here code to get latest level played from the database. If this can't be found, we just let the user play the first level.")
initCanvas: =>
initCanvas: ->
@stage = new createjs.Stage($('#beginner-campaign canvas', @$el)[0])
@createWizard()

View file

@ -38,8 +38,15 @@ module.exports = class RootView extends CocoView
location.hash = ''
location.hash = hash
@buildLanguages()
afterRender: ->
super(arguments...)
@chooseTab(location.hash.replace('#','')) if location.hash
# TODO: automate tabs to put in hashes and navigate to them here
chooseTab: (category) ->
$("a[href='##{category}']", @$el).tab('show')
# TODO: automate tabs to put in hashes when they are clicked
buildLanguages: ->
$select = @$el.find(".language-dropdown").empty()

View file

@ -28,14 +28,20 @@ module.exports = class ThangTypeHomeView extends View
getRenderData: ->
context = super()
context.modelLabel = @modelLabel
switch @modelLabel
when 'Level'
context.currentEditor = 'editor.level_title'
context.currentNew = 'editor.new_level_title'
context.currentSearch = 'editor.level_search_title'
when 'Thang Type'
context.currentEditor = 'editor.thang_title'
context.currentNew = 'editor.new_thang_title'
context.currentSearch = 'editor.thang_search_title'
when 'Article'
context.currentEditor = 'editor.article_title'
context.currentNew = 'editor.new_article_title'
context.currentSearch = 'editor.article_search_title'
@$el.i18n()
context
constructor: (options) ->
@ -77,6 +83,7 @@ module.exports = class ThangTypeHomeView extends View
documents = @collection.models
table = $(@tableTemplate(documents:documents))
@$el.find('table').replaceWith(table)
@$el.find('table').i18n()
removeOldSearch: ->
return unless @collection?

View file

@ -0,0 +1,100 @@
CocoView = require 'views/kinds/CocoView'
Level = require 'models/Level'
LevelSession = require 'models/LevelSession'
CocoCollection = require 'models/CocoCollection'
LeaderboardCollection = require 'collections/LeaderboardCollection'
{teamDataFromLevel} = require './utils'
HIGHEST_SCORE = 1000000
class LevelSessionsCollection extends CocoCollection
url: ''
model: LevelSession
constructor: (levelID) ->
super()
@url = "/db/level/#{levelID}/all_sessions"
module.exports = class LadderTabView extends CocoView
id: 'ladder-tab-view'
template: require 'templates/play/ladder/ladder_tab'
startsLoading: true
constructor: (options, @level, @sessions) ->
super(options)
@teams = teamDataFromLevel @level
@leaderboards = {}
@refreshLadder()
refreshLadder: ->
for team in @teams
@leaderboards[team.id]?.off 'sync'
# teamSession = _.find @sessions.models, (session) -> session.get('team') is team.id
teamSession = null
# console.log "Team session: #{JSON.stringify teamSession}"
@leaderboards[team.id] = new LeaderboardData(@level, team.id, teamSession)
@leaderboards[team.id].once 'sync', @onLeaderboardLoaded, @
onLeaderboardLoaded: -> @renderMaybe()
renderMaybe: ->
leaderboardModels = _.values(@leaderboards)
return unless _.every leaderboardModels, (loader) -> loader.loaded
@startsLoading = false
@render()
getRenderData: ->
ctx = super()
ctx.level = @level
ctx.link = "/play/level/#{@level.get('name')}"
ctx.teams = @teams
team.leaderboard = @leaderboards[team.id] for team in @teams
ctx.levelID = @levelID
ctx
class LeaderboardData
constructor: (@level, @team, @session) ->
_.extend @, Backbone.Events
@topPlayers = new LeaderboardCollection(@level, {order:-1, scoreOffset: HIGHEST_SCORE, team: @team, limit: if @session then 10 else 20})
@topPlayers.fetch()
@topPlayers.comparator = (model) ->
return -model.get('totalScore')
@topPlayers.sort()
@topPlayers.once 'sync', @leaderboardPartLoaded, @
# if @session
# score = @session.get('totalScore') or 25
# @playersAbove = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 4, team: @team})
# @playersAbove.fetch()
# @playersAbove.once 'sync', @leaderboardPartLoaded, @
# @playersBelow = new LeaderboardCollection(@level, {order:-1, scoreOffset: score, limit: 4, team: @team})
# @playersBelow.fetch()
# @playersBelow.once 'sync', @leaderboardPartLoaded, @
leaderboardPartLoaded: ->
if @session
if @topPlayers.loaded # and @playersAbove.loaded and @playersBelow.loaded
@loaded = true
@fetchNames()
else
@loaded = true
@fetchNames()
fetchNames: ->
sessionCollections = [@topPlayers, @playersAbove, @playersBelow]
sessionCollections = (s for s in sessionCollections when s)
ids = []
for collection in sessionCollections
ids.push model.get('creator') for model in collection.models
success = (nameMap) =>
for collection in sessionCollections
session.set('creatorName', nameMap[session.get('creator')]) for session in collection.models
@trigger 'sync'
$.ajax('/db/user/-/names', {
data: {ids: ids}
type: 'POST'
success: success
})

View file

@ -0,0 +1,108 @@
CocoView = require 'views/kinds/CocoView'
Level = require 'models/Level'
LevelSession = require 'models/LevelSession'
LeaderboardCollection = require 'collections/LeaderboardCollection'
{teamDataFromLevel} = require './utils'
module.exports = class MyMatchesTabView extends CocoView
id: 'my-matches-tab-view'
template: require 'templates/play/ladder/my_matches_tab'
startsLoading: true
events:
'click .rank-button': 'rankSession'
constructor: (options, @level, @sessions) ->
super(options)
@refreshMatches()
refreshMatches: ->
@teams = teamDataFromLevel @level
@nameMap = {}
@loadNames()
loadNames: ->
ids = []
for session in @sessions.models
ids.push match.opponents[0].userID for match in session.get('matches') or []
success = (@nameMap) =>
for session in @sessions.models
for match in session.get('matches') or []
opponent = match.opponents[0]
opponent.userName = @nameMap[opponent.userID]
@finishRendering()
$.ajax('/db/user/-/names', {
data: {ids: ids}
type: 'POST'
success: success
})
finishRendering: ->
@startsLoading = false
@render()
getRenderData: ->
ctx = super()
ctx.level = @level
ctx.levelID = @level.get('slug') or @level.id
ctx.teams = @teams
convertMatch = (match) =>
opponent = match.opponents[0]
state = 'win'
state = 'loss' if match.metrics.rank > opponent.metrics.rank
state = 'tie' if match.metrics.rank is opponent.metrics.rank
{
state: state
opponentName: @nameMap[opponent.userID]
opponentID: opponent.userID
when: moment(match.date).fromNow()
sessionID: opponent.sessionID
}
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.matches.reverse()
team.score = (team.session?.get('totalScore') or 10).toFixed(2)
ctx
afterRender: ->
super()
@$el.find('.rank-button').each (i, el) =>
button = $(el)
sessionID = button.data('session-id')
session = _.find @sessions.models, { id: sessionID }
@setRankingButtonText button, if @readyToRank(session) then 'rank' else 'unavailable'
readyToRank: (session) ->
c1 = session?.get('code')
c2 = session?.get('submittedCode')
c1 and not _.isEqual(c1, c2)
rankSession: (e) ->
console.log "Clicked"
button = $(e.target).closest('.rank-button')
sessionID = button.data('session-id')
session = _.find @sessions.models, { id: sessionID }
return unless @readyToRank(session)
@setRankingButtonText(button, 'ranking')
success = => @setRankingButtonText(button, 'ranked')
failure = => @setRankingButtonText(button, 'failed')
$.ajax '/queue/scoring', {
type: 'POST'
data: { session: sessionID }
success: success
failure: failure
}
setRankingButtonText: (rankButton, spanClass) ->
rankButton.find('span').addClass('hidden')
rankButton.find(".#{spanClass}").removeClass('hidden')
rankButton.toggleClass 'disabled', spanClass isnt 'rank'

View file

@ -0,0 +1,162 @@
View = require 'views/kinds/ModalView'
template = require 'templates/play/ladder/play_modal'
ThangType = require 'models/ThangType'
{me} = require 'lib/auth'
LeaderboardCollection = require 'collections/LeaderboardCollection'
{teamDataFromLevel} = require './utils'
module.exports = class LadderPlayModal extends View
id: "ladder-play-modal"
template: template
closeButton: true
startsLoading: true
constructor: (options, @level, @session, @team) ->
super(options)
@nameMap = {}
@otherTeam = if team is 'ogres' then 'humans' else 'ogres'
@startLoadingChallengersMaybe()
@wizardType = ThangType.loadUniversalWizard()
# PART 1: Load challengers from the db unless some are in the matches
startLoadingChallengersMaybe: ->
matches = @session?.get('matches')
if matches?.length then @loadNames() else @loadChallengers()
loadChallengers: ->
@challengersCollection = new ChallengersData(@level, @team, @otherTeam, @session)
@challengersCollection.on 'sync', @loadNames, @
# PART 2: Loading the names of the other users
loadNames: ->
@challengers = @getChallengers()
ids = (challenger.opponentID for challenger in _.values @challengers)
success = (@nameMap) =>
for challenger in _.values(@challengers)
challenger.opponentName = @nameMap[challenger.opponentID]?.name or 'Anoner'
challenger.opponentWizard = @nameMap[challenger.opponentID]?.wizard or {}
@checkWizardLoaded()
$.ajax('/db/user/-/names', {
data: {ids: ids, wizard: true}
type: 'POST'
success: success
})
# PART 3: Make sure wizard is loaded
checkWizardLoaded: ->
if @wizardType.loaded then @finishRendering() else @wizardType.once 'sync', @finishRendering, @
# PART 4: Render
finishRendering: ->
@startsLoading = false
@render()
getRenderData: ->
ctx = super()
ctx.level = @level
ctx.levelID = @level.get('slug') or @level.id
ctx.teamName = _.string.titleize @team
ctx.teamID = @team
ctx.otherTeamID = @otherTeam
teamsList = teamDataFromLevel @level
teams = {}
teams[team.id] = team for team in teamsList
ctx.teamColor = teams[@team].primaryColor
ctx.teamBackgroundColor = teams[@team].bgColor
ctx.opponentTeamColor = teams[@otherTeam].primaryColor
ctx.opponentTeamBackgroundColor = teams[@otherTeam].bgColor
ctx.challengers = @challengers or {}
for challenger in _.values ctx.challengers
continue unless challenger and @wizardType.loaded
if (not challenger.opponentImageSource) and challenger.opponentWizard?.colorConfig
challenger.opponentImageSource = @wizardType.getPortraitSource(
{colorConfig: challenger.opponentWizard.colorConfig})
if @wizardType.loaded
ctx.genericPortrait = @wizardType.getPortraitSource()
myColorConfig = me.get('wizard')?.colorConfig
ctx.myPortrait = if myColorConfig then @wizardType.getPortraitSource({colorConfig: myColorConfig}) else ctx.genericPortrait
ctx.myName = me.get('name') || 'Newcomer'
ctx
# Choosing challengers
getChallengers: ->
# make an object of challengers to everything needed to link to them
challengers = {}
if @challengersCollection
easyInfo = @challengeInfoFromSession(@challengersCollection.easyPlayer.models[0])
mediumInfo = @challengeInfoFromSession(@challengersCollection.mediumPlayer.models[0])
hardInfo = @challengeInfoFromSession(@challengersCollection.hardPlayer.models[0])
else
matches = @session.get('matches')
won = (m for m in matches when m.metrics.rank < m.opponents[0].metrics.rank)
lost = (m for m in matches when m.metrics.rank > m.opponents[0].metrics.rank)
tied = (m for m in matches when m.metrics.rank is m.opponents[0].metrics.rank)
easyInfo = @challengeInfoFromMatches(won)
mediumInfo = @challengeInfoFromMatches(tied)
hardInfo = @challengeInfoFromMatches(lost)
@addChallenger easyInfo, challengers, 'easy'
@addChallenger mediumInfo, challengers, 'medium'
@addChallenger hardInfo, challengers, 'hard'
challengers
addChallenger: (info, challengers, title) ->
# check for duplicates first
return unless info
for key, value of challengers
return if value.sessionID is info.sessionID
challengers[title] = info
challengeInfoFromSession: (session) ->
# given a model from the db, return info needed for a link to the match
return unless session
return {
sessionID: session.id
opponentID: session.get('creator')
}
challengeInfoFromMatches: (matches) ->
return unless matches?.length
match = _.sample matches
opponent = match.opponents[0]
return {
sessionID: opponent.sessionID
opponentID: opponent.userID
}
class ChallengersData
constructor: (@level, @team, @otherTeam, @session) ->
_.extend @, Backbone.Events
score = @session?.get('totalScore') or 25
@easyPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score - 5, limit: 1, team: @otherTeam})
@easyPlayer.fetch()
@easyPlayer.once 'sync', @challengerLoaded, @
@mediumPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 1, team: @otherTeam})
@mediumPlayer.fetch()
@mediumPlayer.once 'sync', @challengerLoaded, @
@hardPlayer = new LeaderboardCollection(@level, {order:-1, scoreOffset: score + 5, limit: 1, team: @otherTeam})
@hardPlayer.fetch()
@hardPlayer.once 'sync', @challengerLoaded, @
challengerLoaded: ->
if @allLoaded()
@loaded = true
@trigger 'sync'
playerIDs: ->
collections = [@easyPlayer, @mediumPlayer, @hardPlayer]
(c.models[0].get('creator') for c in collections when c?.models[0])
allLoaded: ->
_.all [@easyPlayer.loaded, @mediumPlayer.loaded, @hardPlayer.loaded]

View file

@ -1,195 +0,0 @@
RootView = require 'views/kinds/RootView'
Level = require 'models/Level'
LevelSession = require 'models/LevelSession'
LeaderboardCollection = require 'collections/LeaderboardCollection'
module.exports = class LadderTeamView extends RootView
id: 'ladder-team-view'
template: require 'templates/play/ladder/team'
startsLoading: true
events:
'click #rank-button': 'rankSession'
# PART 1: Loading Level/Session
constructor: (options, @levelID, @team) ->
super(options)
@otherTeam = if team is 'ogres' then 'humans' else 'ogres'
@level = new Level(_id:@levelID)
@level.fetch()
@level.once 'sync', @onLevelLoaded, @
url = "/db/level/#{@levelID}/session?team=#{@team}"
@session = new LevelSession()
@session.url = -> url
@session.fetch()
@session.once 'sync', @onSessionLoaded, @
onLevelLoaded: -> @startLoadingChallengersMaybe()
onSessionLoaded: -> @startLoadingChallengersMaybe()
# PART 2: Loading some challengers if we don't have any matches yet
startLoadingChallengersMaybe: ->
return unless @level.loaded and @session.loaded
matches = @session.get('matches')
if matches?.length then @loadNames() else @loadChallengers()
loadChallengers: ->
@challengers = new ChallengersData(@level, @team, @otherTeam, @session)
@challengers.on 'sync', @loadNames, @
# PART 3: Loading the names of the other users
loadNames: ->
ids = []
ids.push match.opponents[0].userID for match in @session.get('matches') or []
ids = ids.concat(@challengers.playerIDs()) if @challengers
success = (@nameMap) =>
for match in @session.get('matches') or []
opponent = match.opponents[0]
opponent.userName = @nameMap[opponent.userID]
@finishRendering()
$.ajax('/db/user/-/names', {
data: {ids: ids}
type: 'POST'
success: success
})
# PART 4: Rendering
finishRendering: ->
@startsLoading = false
@render()
getRenderData: ->
ctx = super()
ctx.level = @level
ctx.levelID = @levelID
ctx.teamName = _.string.titleize @team
ctx.teamID = @team
ctx.otherTeamID = @otherTeam
ctx.challengers = if not @startsLoading then @getChallengers() else {}
ctx.readyToRank = @readyToRank()
convertMatch = (match) =>
opponent = match.opponents[0]
state = 'win'
state = 'loss' if match.metrics.rank > opponent.metrics.rank
state = 'tie' if match.metrics.rank is opponent.metrics.rank
{
state: state
opponentName: @nameMap[opponent.userID]
opponentID: opponent.userID
when: moment(match.date).fromNow()
sessionID: opponent.sessionID
}
ctx.matches = (convertMatch(match) for match in @session.get('matches') or [])
ctx.matches.reverse()
ctx.score = (@session.get('totalScore') or 10).toFixed(2)
ctx
afterRender: ->
super()
@setRankingButtonText(if @readyToRank() then 'rank' else 'unavailable')
readyToRank: ->
c1 = @session.get('code')
c2 = @session.get('submittedCode')
c1 and not _.isEqual(c1, c2)
getChallengers: ->
# make an object of challengers to everything needed to link to them
challengers = {}
if @challengers
easyInfo = @challengeInfoFromSession(@challengers.easyPlayer.models[0])
mediumInfo = @challengeInfoFromSession(@challengers.mediumPlayer.models[0])
hardInfo = @challengeInfoFromSession(@challengers.hardPlayer.models[0])
else
matches = @session.get('matches')
won = (m for m in matches when m.metrics.rank < m.opponents[0].metrics.rank)
lost = (m for m in matches when m.metrics.rank > m.opponents[0].metrics.rank)
tied = (m for m in matches when m.metrics.rank is m.opponents[0].metrics.rank)
easyInfo = @challengeInfoFromMatches(won)
mediumInfo = @challengeInfoFromMatches(tied)
hardInfo = @challengeInfoFromMatches(lost)
@addChallenger easyInfo, challengers, 'easy'
@addChallenger mediumInfo, challengers, 'medium'
@addChallenger hardInfo, challengers, 'hard'
challengers
addChallenger: (info, challengers, title) ->
# check for duplicates first
return unless info
for key, value of challengers
return if value.sessionID is info.sessionID
challengers[title] = info
challengeInfoFromSession: (session) ->
# given a model from the db, return info needed for a link to the match
return unless session
return {
sessionID: session.id
opponentName: @nameMap[session.get('creator')] or 'Anoner'
opponentID: session.get('creator')
}
challengeInfoFromMatches: (matches) ->
return unless matches?.length
match = _.sample matches
opponent = match.opponents[0]
return {
sessionID: opponent.sessionID
opponentName: opponent.userName or 'Anoner'
opponentID: opponent.userID
}
rankSession: ->
return unless @readyToRank()
@setRankingButtonText('ranking')
success = => @setRankingButtonText('ranked')
failure = => @setRankingButtonText('failed')
$.ajax '/queue/scoring', {
type: 'POST'
data: { session: @session.id }
success: success
failure: failure
}
setRankingButtonText: (spanClass) ->
rankButton = $('#rank-button')
rankButton.find('span').addClass('hidden')
rankButton.find(".#{spanClass}").removeClass('hidden')
rankButton.toggleClass 'disabled', spanClass isnt 'rank'
class ChallengersData
constructor: (@level, @team, @otherTeam, @session) ->
_.extend @, Backbone.Events
score = @session?.get('totalScore') or 25
@easyPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score - 5, limit: 1, team: @otherTeam})
@easyPlayer.fetch()
@easyPlayer.once 'sync', @challengerLoaded, @
@mediumPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 1, team: @otherTeam})
@mediumPlayer.fetch()
@mediumPlayer.once 'sync', @challengerLoaded, @
@hardPlayer = new LeaderboardCollection(@level, {order:-1, scoreOffset: score + 5, limit: 1, team: @otherTeam})
@hardPlayer.fetch()
@hardPlayer.once 'sync', @challengerLoaded, @
challengerLoaded: ->
if @allLoaded()
@loaded = true
@trigger 'sync'
playerIDs: ->
collections = [@easyPlayer, @mediumPlayer, @hardPlayer]
(c.models[0].get('creator') for c in collections when c?.models[0])
allLoaded: ->
_.all [@easyPlayer.loaded, @mediumPlayer.loaded, @hardPlayer.loaded]

View file

@ -0,0 +1,22 @@
{hslToHex} = require 'lib/utils'
module.exports.teamDataFromLevel = (level) ->
alliedSystem = _.find level.get('systems'), (value) -> value.config?.teams?
teamNames = (teamName for teamName, teamConfig of alliedSystem.config.teams when teamConfig.playable)
teamConfigs = alliedSystem.config.teams
teams = []
for team in teamNames or []
otherTeam = if team is 'ogres' then 'humans' else 'ogres'
color = teamConfigs[team].color
bgColor = hslToHex([color.hue, color.saturation, color.lightness + (1 - color.lightness) * 0.5])
primaryColor = hslToHex([color.hue, 0.5, 0.5])
teams.push({
id: team
name: _.string.titleize(team)
otherTeam: otherTeam
bgColor: bgColor
primaryColor: primaryColor
})
teams

View file

@ -3,8 +3,11 @@ Level = require 'models/Level'
Simulator = require 'lib/simulator/Simulator'
LevelSession = require 'models/LevelSession'
CocoCollection = require 'models/CocoCollection'
LeaderboardCollection = require 'collections/LeaderboardCollection'
{hslToHex} = require 'lib/utils'
{teamDataFromLevel} = require './ladder/utils'
LadderTabView = require './ladder/ladder_tab'
MyMatchesTabView = require './ladder/my_matches_tab'
LadderPlayModal = require './ladder/play_modal'
HIGHEST_SCORE = 1000000
@ -14,7 +17,7 @@ class LevelSessionsCollection extends CocoCollection
constructor: (levelID) ->
super()
@url = "/db/level/#{levelID}/all_sessions"
@url = "/db/level/#{levelID}/my_sessions"
module.exports = class LadderView extends RootView
id: 'ladder-view'
@ -24,9 +27,59 @@ module.exports = class LadderView extends RootView
events:
'click #simulate-button': 'onSimulateButtonClick'
'click #simulate-all-button': 'onSimulateAllButtonClick'
'click .play-button': 'onClickPlayButton'
constructor: (options, @levelID) ->
super(options)
@level = new Level(_id:@levelID)
@level.fetch()
@level.once 'sync', @onLevelLoaded, @
@sessions = new LevelSessionsCollection(levelID)
@sessions.fetch({})
@sessions.once 'sync', @onMySessionsLoaded, @
@simulator = new Simulator()
@simulator.on 'statusUpdate', @updateSimulationStatus, @
@teams = []
onLevelLoaded: -> @renderMaybe()
onMySessionsLoaded: -> @renderMaybe()
renderMaybe: ->
return unless @level.loaded and @sessions.loaded
@teams = teamDataFromLevel @level
@startsLoading = false
@render()
getRenderData: ->
ctx = super()
ctx.level = @level
ctx.link = "/play/level/#{@level.get('name')}"
ctx.simulationStatus = @simulationStatus
ctx.teams = @teams
ctx.levelID = @levelID
ctx.levelDescription = marked(@level.get('description')) if @level.get('description')
ctx
afterRender: ->
super()
return if @startsLoading
@insertSubView(@ladderTab = new LadderTabView({}, @level, @sessions))
@insertSubView(@myMatchesTab = new MyMatchesTabView({}, @level, @sessions))
setInterval(@fetchSessionsAndRefreshViews.bind(@), 10000)
fetchSessionsAndRefreshViews: ->
@sessions.fetch({"success": @refreshViews})
refreshViews: =>
@ladderTab.refreshLadder()
@myMatchesTab.refreshMatches()
console.log "refreshed views!"
# Simulations
onSimulateAllButtonClick: (e) ->
submitIDs = _.pluck @leaderboards[@teams[0]].topPlayers.models, "id"
submitIDs = _.pluck @leaderboards[@teams[0].id].topPlayers.models, "id"
for ID in submitIDs
$.ajax
url: '/queue/scoring'
@ -58,140 +111,9 @@ module.exports = class LadderView extends RootView
console.log "There was a problem with the named simulation status: #{e}"
$("#simulation-status-text").text @simulationStatus
constructor: (options, @levelID) ->
super(options)
@level = new Level(_id:@levelID)
@level.fetch()
@level.once 'sync', @onLevelLoaded, @
@simulator = new Simulator()
@simulator.on 'statusUpdate', @updateSimulationStatus, @
# @sessions = new LevelSessionsCollection(levelID)
# @sessions.fetch({})
# @sessions.once 'sync', @onMySessionsLoaded, @
onLevelLoaded: -> @startLoadingPhaseTwoMaybe()
onMySessionsLoaded: ->
@startLoadingPhaseTwoMaybe()
startLoadingPhaseTwoMaybe: ->
return unless @level.loaded # and @sessions.loaded
@loadPhaseTwo()
loadPhaseTwo: ->
alliedSystem = _.find @level.get('systems'), (value) -> value.config?.teams?
teams = []
for teamName, teamConfig of alliedSystem.config.teams
continue unless teamConfig.playable
teams.push teamName
@teams = teams
@teamConfigs = alliedSystem.config.teams
@leaderboards = {}
@challengers = {}
for team in teams
# teamSession = _.find @sessions.models, (session) -> session.get('team') is team
teamSession = null
console.log "Team session: #{JSON.stringify teamSession}"
@leaderboards[team] = new LeaderboardData(@level, team, teamSession)
@leaderboards[team].once 'sync', @onLeaderboardLoaded, @
onChallengersLoaded: -> @renderMaybe()
onLeaderboardLoaded: -> @renderMaybe()
renderMaybe: ->
loaders = _.values(@leaderboards) # .concat(_.values(@challengers))
return unless _.every loaders, (loader) -> loader.loaded
@startsLoading = false
@render()
getRenderData: ->
ctx = super()
ctx.level = @level
description = @level.get('description')
ctx.description = if description then marked(description) else ''
ctx.link = "/play/level/#{@level.get('name')}"
ctx.simulationStatus = @simulationStatus
ctx.teams = []
ctx.levelID = @levelID
for team in @teams or []
otherTeam = if team is 'ogres' then 'humans' else 'ogres'
color = @teamConfigs[team].color
bgColor = hslToHex([color.hue, color.saturation, color.lightness + (1 - color.lightness) * 0.5])
primaryColor = hslToHex([color.hue, 0.5, 0.5])
ctx.teams.push({
id: team
name: _.string.titleize(team)
leaderboard: @leaderboards[team]
otherTeam: otherTeam
bgColor: bgColor
primaryColor: primaryColor
})
ctx
class LeaderboardData
constructor: (@level, @team, @session) ->
_.extend @, Backbone.Events
@topPlayers = new LeaderboardCollection(@level, {order:-1, scoreOffset: HIGHEST_SCORE, team: @team, limit: if @session then 10 else 20})
@topPlayers.fetch()
@topPlayers.comparator = (model) ->
return -model.get('totalScore')
@topPlayers.sort()
@topPlayers.once 'sync', @leaderboardPartLoaded, @
# if @session
# score = @session.get('totalScore') or 25
# @playersAbove = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 4, team: @team})
# @playersAbove.fetch()
# @playersAbove.once 'sync', @leaderboardPartLoaded, @
# @playersBelow = new LeaderboardCollection(@level, {order:-1, scoreOffset: score, limit: 4, team: @team})
# @playersBelow.fetch()
# @playersBelow.once 'sync', @leaderboardPartLoaded, @
leaderboardPartLoaded: ->
if @session
if @topPlayers.loaded # and @playersAbove.loaded and @playersBelow.loaded
@loaded = true
@fetchNames()
else
@loaded = true
@fetchNames()
fetchNames: ->
sessionCollections = [@topPlayers, @playersAbove, @playersBelow]
sessionCollections = (s for s in sessionCollections when s)
ids = []
for collection in sessionCollections
ids.push model.get('creator') for model in collection.models
success = (nameMap) =>
for collection in sessionCollections
session.set('creatorName', nameMap[session.get('creator')]) for session in collection.models
@trigger 'sync'
$.ajax('/db/user/-/names', {
data: {ids: ids}
type: 'POST'
success: success
})
class ChallengersData
constructor: (@level, @team, @session) ->
_.extend @, Backbone.Events
score = @session?.get('totalScore') or 25
@easyPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score - 5, limit: 1, team: @team})
@easyPlayer.fetch()
@easyPlayer.once 'sync', @challengerLoaded, @
@mediumPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 1, team: @team})
@mediumPlayer.fetch()
@mediumPlayer.once 'sync', @challengerLoaded, @
@hardPlayer = new LeaderboardCollection(@level, {order:-1, scoreOffset: score + 5, limit: 1, team: @team})
@hardPlayer.fetch()
@hardPlayer.once 'sync', @challengerLoaded, @
challengerLoaded: ->
if @easyPlayer.loaded and @mediumPlayer.loaded and @hardPlayer.loaded
@loaded = true
@trigger 'sync'
onClickPlayButton: (e) ->
button = $(e.target).closest('.play-button')
teamID = button.data('team')
session = (s for s in @sessions.models when s.get('team') is teamID)[0]
modal = new LadderPlayModal({}, @level, session, teamID)
@openModalView modal

View file

@ -135,23 +135,23 @@ module.exports = class HUDView extends View
props = @$el.find('.thang-props')
props.find(":not(.thang-name)").remove()
props.find('.thang-name').text(if @thang.type then "#{@thang.id} - #{@thang.type}" else @thang.id)
column = null
for prop in @thang.hudProperties ? []
propNames = @thang.hudProperties ? []
nColumns = Math.ceil propNames.length / 5
columns = ($('<div class="thang-props-column"></div>').appendTo(props) for i in [0 ... nColumns])
for prop, i in propNames
continue if prop is 'action'
pel = @createPropElement prop
continue unless pel?
if pel.find('.bar').is('*') and props.find('.bar').is('*')
props.find('.bar-prop').last().after pel # Keep bars together
else
column ?= $('<div class="thang-props-column"></div>').appendTo props
column.append pel
column = null if column.find('.prop').length is 5
columns[i % nColumns].append pel
null
createActions: ->
actions = @$el.find('.thang-actions tbody').empty()
showActions = @thang.world and not _.isEmpty(@thang.actions) and 'action' in @thang.hudProperties ? []
@$el.find('.thang-actions').toggle showActions
@$el.find('.thang-actions').toggleClass 'secret', showActions
return unless showActions
@buildActionTimespans()
for actionName, action of @thang.actions
@ -263,6 +263,7 @@ module.exports = class HUDView extends View
labelText = prop + ": " + @formatValue(prop, val) + " / " + @formatValue(prop, max)
if regen
labelText += " (+" + @formatValue(prop, regen) + "/s)"
pel.find('.bar-prop-value').text(Math.round(max)) if max
else
s = @formatValue(prop, val)
labelText = "#{prop}: #{s}"

View file

@ -154,6 +154,13 @@ module.exports = class PlayView extends View
image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png'
description: "Strike at the weak point in an array of enemies. - by Aftermath"
}
{
name: 'Bubble Sort Bootcamp Battle'
difficulty: 3
id: 'bubble-sort-bootcamp-battle'
image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png'
description: "Write a bubble sort to organize your soldiers. - by Alexandru"
}
{
name: 'Enemy Artillery'
difficulty: 1

View file

@ -261,7 +261,7 @@ module.exports = class Handler
tv4 = require('tv4').tv4
res = tv4.validateMultiple(input, @jsonSchema)
res
@isID: (id) -> _.isString(id) and id.length is 24 and id.match(/[a-z0-9]/gi)?.length is 24
getDocumentForIdOrSlug: (idOrSlug, done) ->

View file

@ -27,7 +27,7 @@ LevelHandler = class LevelHandler extends Handler
getByRelationship: (req, res, args...) ->
return @getSession(req, res, args[0]) if args[1] is 'session'
return @getLeaderboard(req, res, args[0]) if args[1] is 'leaderboard'
return @getAllSessions(req, res, args[0]) if args[1] is 'all_sessions'
return @getMySessions(req, res, args[0]) if args[1] is 'my_sessions'
return @getFeedback(req, res, args[0]) if args[1] is 'feedback'
return @sendNotFoundError(res)
@ -86,26 +86,15 @@ LevelHandler = class LevelHandler extends Handler
# associated with the handler, because the handler might return a different type
# of model, like in this case. Refactor to move that logic to the model instead.
getAllSessions: (req, res, id) ->
getMySessions: (req, res, id) ->
@fetchLevelByIDAndHandleErrors id, req, res, (err, level) =>
sessionQuery =
level:
original: level.original.toString()
majorVersion: level.version.major
submitted: true
propertiesToReturn = [
'_id'
'totalScore'
'submitted'
'team'
'creatorName'
]
query = Session
.find(sessionQuery)
.select(propertiesToReturn.join ' ')
creator: req.user._id+''
query = Session.find(sessionQuery)
query.exec (err, results) =>
if err then @sendDatabaseError(res, err) else @sendSuccess res, results

View file

@ -84,7 +84,7 @@ module.exports.dispatchTaskToConsumer = (req, res) ->
message.changeMessageVisibilityTimeout scoringTaskTimeoutInSeconds, (err) ->
if err? then return errors.serverError res, "There was an error changing the message visibility timeout."
console.log "Changed visibility timeout"
constructTaskLogObject getUserIDFromRequest(req),message.getReceiptHandle(), (taskLogError, taskLogObject) ->
constructTaskLogObject getUserIDFromRequest(req), message.getReceiptHandle(), (taskLogError, taskLogObject) ->
if taskLogError? then return errors.serverError res, "There was an error creating the task log object."
taskObject.taskID = taskLogObject._id
@ -107,40 +107,48 @@ 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."
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
findNearestBetterSessionID 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!"}
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.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
findNearestBetterSessionID 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!"}
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!"}
determineIfSessionShouldContinueAndUpdateLog = (sessionID, sessionRank, cb) ->

View file

@ -13,6 +13,7 @@ module.exports.setup = (app) ->
fileGet = (req, res) ->
path = req.path[6..]
path = decodeURI path
isFolder = false
try
objectId = mongoose.Types.ObjectId(path)

View file

@ -114,11 +114,19 @@ UserHandler = class UserHandler extends Handler
ids = ids.split(',') if _.isString ids
ids = _.uniq ids
# TODO: Extend and repurpose this handler to return other public info about a user more flexibly,
# say by a query parameter that lists public properties to return.
returnWizard = req.query.wizard or req.body.wizard
query = if returnWizard then {name:1, wizard:1} else {name:1}
makeFunc = (id) ->
(callback) ->
User.findById(id, {name:1}).exec (err, document) ->
User.findById(id, query).exec (err, document) ->
return done(err) if err
callback(null, document?.get('name') or '')
if document and returnWizard
callback(null, {name:document.get('name'), wizard:document.get('wizard') or {}})
else
callback(null, document?.get('name') or '')
funcs = {}
for id in ids

49
server_config.coffee Normal file
View file

@ -0,0 +1,49 @@
config = {}
config.unittest = process.argv.indexOf("--unittest") > -1
config.port = process.env.COCO_PORT or process.env.COCO_NODE_PORT or 3000
config.ssl_port = process.env.COCO_SSL_PORT or process.env.COCO_SSL_NODE_PORT or 3443
config.mongo =
port: process.env.COCO_MONGO_PORT or 27017
host: process.env.COCO_MONGO_HOST or "localhost"
db: process.env.COCO_MONGO_DATABASE_NAME or "coco"
mongoose_replica_string: process.env.COCO_MONGO_MONGOOSE_REPLICA_STRING or ""
if config.unittest
config.port += 1
config.ssl_port += 1
config.mongo.host = "localhost"
else
config.mongo.username = process.env.COCO_MONGO_USERNAME or ""
config.mongo.password = process.env.COCO_MONGO_PASSWORD or ""
config.mail =
service: process.env.COCO_MAIL_SERVICE_NAME or "Zoho"
username: process.env.COCO_MAIL_SERVICE_USERNAME or ""
password: process.env.COCO_MAIL_SERVICE_PASSWORD or ""
mailchimpAPIKey: process.env.COCO_MAILCHIMP_API_KEY or ""
mailchimpWebhook: process.env.COCO_MAILCHIMP_WEBHOOK or "/mail/webhook"
sendwithusAPIKey: process.env.COCO_SENDWITHUS_API_KEY or ""
config.queue =
accessKeyId: process.env.COCO_AWS_ACCESS_KEY_ID or ""
secretAccessKey: process.env.COCO_AWS_SECRET_ACCESS_KEY or ""
region: "us-east-1"
simulationQueueName: "simulationQueue"
config.mongoQueue =
queueDatabaseName: "coco_queue"
config.salt = process.env.COCO_SALT or "pepper"
config.cookie_secret = process.env.COCO_COOKIE_SECRET or "chips ahoy"
config.isProduction = config.mongo.host isnt "localhost"
if not config.unittest and not config.isProduction
# change artificially slow down non-static requests for testing
config.slow_down = false
module.exports = config

View file

@ -1,52 +0,0 @@
var config = {};
config.unittest = process.argv.indexOf('--unittest') > -1;
config.port = process.env.COCO_PORT || process.env.COCO_NODE_PORT || 3000;
config.ssl_port =
process.env.COCO_SSL_PORT || process.env.COCO_SSL_NODE_PORT || 3443;
config.mongo = {};
config.mongo.port = process.env.COCO_MONGO_PORT || 27017;
config.mongo.host = process.env.COCO_MONGO_HOST || 'localhost';
config.mongo.db = process.env.COCO_MONGO_DATABASE_NAME || 'coco';
config.mongo.mongoose_replica_string = process.env.COCO_MONGO_MONGOOSE_REPLICA_STRING || '';
if(config.unittest) {
config.port += 1;
config.ssl_port += 1;
config.mongo.host = 'localhost';
}
else {
config.mongo.username = process.env.COCO_MONGO_USERNAME || '';
config.mongo.password = process.env.COCO_MONGO_PASSWORD || '';
}
config.mail = {};
config.mail.service = process.env.COCO_MAIL_SERVICE_NAME || "Zoho";
config.mail.username = process.env.COCO_MAIL_SERVICE_USERNAME || "";
config.mail.password = process.env.COCO_MAIL_SERVICE_PASSWORD || "";
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';
config.isProduction = config.mongo.host != 'localhost';
if(!config.unittest && !config.isProduction) {
// change artificially slow down non-static requests for testing
config.slow_down = false;
}
module.exports = config;

View file

@ -8,21 +8,37 @@ database = require './server/commons/database'
baseRoute = require './server/routes/base'
user = require './server/users/user_handler'
logging = require './server/commons/logging'
config = require './server_config'
###Middleware setup functions implementation###
setupRequestTimeoutMiddleware = (app) ->
app.use (req, res, next) ->
req.setTimeout 15000, ->
console.log 'timed out!'
req.abort()
self.emit('pass',message)
next()
# 2014-03-03: Try not using this and see if it's still a problem
#setupRequestTimeoutMiddleware = (app) ->
# app.use (req, res, next) ->
# req.setTimeout 15000, ->
# console.log 'timed out!'
# req.abort()
# self.emit('pass',message)
# next()
productionLogging = (tokens, req, res)->
status = res.statusCode
color = 32
if status >= 500 then color = 31
else if status >= 400 then color = 33
else if status >= 300 then color = 36
elapsed = (new Date()) - req._startTime
elapsedColor = if elapsed < 500 then 90 else 31
if (status isnt 200 and status isnt 304) or elapsed > 500
return "\x1b[90m#{req.method} #{req.originalUrl} \x1b[#{color}m#{res.statusCode} \x1b[#{elapsedColor}m#{elapsed}ms\x1b[0m"
null
setupExpressMiddleware = (app) ->
setupRequestTimeoutMiddleware app
app.use(express.logger('dev'))
#setupRequestTimeoutMiddleware app
if config.isProduction
express.logger.format('prod', productionLogging)
app.use(express.logger('prod'))
else
app.use(express.logger('dev'))
app.use(express.static(path.join(__dirname, 'public')))
app.use(useragent.express())
@ -31,6 +47,8 @@ setupExpressMiddleware = (app) ->
app.use(express.bodyParser())
app.use(express.methodOverride())
app.use(express.cookieSession({secret:'defenestrate'}))
if config.isProduction
app.use(express.compress())
setupPassportMiddleware = (app) ->
app.use(authentication.initialize())
@ -99,6 +117,5 @@ exports.setExpressConfigurationOptions = (app) ->
app.set('views', __dirname + '/app/views')
app.set('view engine', 'jade')
app.set('view options', { layout: false })
app.set('env', if config.isProduction then 'production' else 'development')
app.set('json spaces', 0)