Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-08-19 08:12:58 -07:00
commit e15e45bd70
25 changed files with 2838 additions and 80 deletions

File diff suppressed because it is too large Load diff

View file

@ -56,8 +56,8 @@ module.exports = class Simulator extends CocoClass
simulateSingleGame: ->
return if @destroyed
@trigger 'statusUpdate', 'Simulating...'
@assignWorldAndLevelFromLevelLoaderAndDestroyIt()
@trigger 'statusUpdate', 'Simulating...'
@setupGod()
try
@commenceSingleSimulation()
@ -71,6 +71,7 @@ module.exports = class Simulator extends CocoClass
handleSingleSimulationError: (error) ->
console.error 'There was an error simulating a single game!', error
return if @destroyed
if @options.headlessClient and @options.simulateOnlyOneGame
console.log 'GAMERESULT:tie'
process.exit(0)
@ -78,6 +79,7 @@ module.exports = class Simulator extends CocoClass
handleSingleSimulationInfiniteLoop: ->
console.log 'There was an infinite loop in the single game!'
return if @destroyed
if @options.headlessClient and @options.simulateOnlyOneGame
console.log 'GAMERESULT:tie'
process.exit(0)
@ -174,8 +176,8 @@ module.exports = class Simulator extends CocoClass
return if @destroyed
info = 'All resources loaded, simulating!'
console.log info
@trigger 'statusUpdate', info, @task.getSessions()
@assignWorldAndLevelFromLevelLoaderAndDestroyIt()
@trigger 'statusUpdate', info, @task.getSessions()
@setupGod()
try
@ -227,6 +229,7 @@ module.exports = class Simulator extends CocoClass
@hd = new @memwatch.HeapDiff()
onInfiniteLoop: ->
return if @destroyed
console.warn 'Skipping infinitely looping game.'
@trigger 'statusUpdate', "Infinite loop detected; grabbing a new game in #{@retryDelayInSeconds} seconds."
_.delay @cleanupAndSimulateAnotherTask, @retryDelayInSeconds * 1000
@ -240,7 +243,11 @@ module.exports = class Simulator extends CocoClass
@sendResultsBackToServer taskResults
sendResultsBackToServer: (results) ->
@trigger 'statusUpdate', 'Simulation completed, sending results back to server!'
status = 'Recording:'
for session in results.sessions
states = ['wins', if _.find(results.sessions, (s) -> s.metrics.rank is 0) then 'loses' else 'draws']
status += " #{session.name} #{states[session.metrics.rank]}"
@trigger 'statusUpdate', status
console.log 'Sending result back to server:'
console.log JSON.stringify results

View file

@ -874,6 +874,7 @@
tournament_ended: "Tournament ended"
tournament_rules: "Tournament Rules"
tournament_blurb: "Write code, collect gold, build armies, crush foes, win prizes, and upgrade your career in our $40,000 Greed tournament! Check out the details"
tournament_blurb_criss_cross: "Win bids, construct paths, outwit opponents, grab gems, and upgrade your career in our Criss-Cross tournament! Check out the details"
tournament_blurb_blog: "on our blog"
rules: "Rules"
winners: "Winners"

View file

@ -264,7 +264,10 @@ module.exports = class ThangType extends CocoModel
@wizardType.fetch()
@wizardType
getPortraitURL: -> "/file/db/thang.type/#{@get('original')}/portrait.png"
getPortraitURL: ->
if iconURL = @get('rasterIcon')
return "/file/#{iconURL}"
"/file/db/thang.type/#{@get('original')}/portrait.png"
# Item functions

View file

@ -49,7 +49,7 @@ _.extend AchievementSchema.properties,
default: 'Probably the coolest you\'ll ever get.'
userField: c.shortString()
related: c.objectId(description: 'Related entity')
icon: {type: 'string', format: 'image-file', title: 'Icon'}
icon: {type: 'string', format: 'image-file', title: 'Icon', description: 'Image should be a 100x100 transparent png.'}
category:
enum: ['level', 'ladder', 'contributor']
description: 'For categorizing and display purposes'

View file

@ -27,7 +27,7 @@ EventPrereqSchema = c.object {title: 'Event Prerequisite', format: 'event-prereq
GoalSchema = c.object {title: 'Goal', description: 'A goal that the player can accomplish.', required: ['name', 'id']},
name: c.shortString(title: 'Name', description: 'Name of the goal that the player will see, like \"Defeat eighteen dragons\".')
i18n: {type: 'object', format: 'i18n', props: ['name'], description: 'Help translate this goal'}
id: c.shortString(title: 'ID', description: 'Unique identifier for this goal, like \"defeat-dragons\".') # unique somehow?
id: c.shortString(title: 'ID', description: 'Unique identifier for this goal, like \"defeat-dragons\".', pattern: '^[a-z0-9-]+$') # unique somehow?
worldEndsAfter: {title: 'World Ends After', description: 'When included, ends the world this many seconds after this goal succeeds or fails.', type: 'number', minimum: 0, exclusiveMinimum: true, maximum: 300, default: 3}
howMany: {title: 'How Many', description: 'When included, require only this many of the listed goal targets instead of all of them.', type: 'integer', minimum: 1}
hiddenGoal: {title: 'Hidden', description: 'Hidden goals don\'t show up in the goals area for the player until they\'re failed. (Usually they\'re obvious, like "don\'t die".)', 'type': 'boolean', default: false}

View file

@ -124,6 +124,7 @@ _.extend ThangTypeSchema.properties,
type: 'number'
positions: PositionsSchema
raster: {type: 'string', format: 'image-file', title: 'Raster Image'}
rasterIcon: { type: 'string', format: 'image-file', title: 'Raster Image Icon' }
colorGroups: c.object
title: 'Color Groups'
additionalProperties:

View file

@ -3,18 +3,24 @@
background-color: #f5f5f5
padding: 10px 20px 10px 0
.treema-markdown.treema-edit, .treema-coffee.treema-edit, .treema-javascript.treema-edit
.treema-markdown
border: 1px solid gray
padding: 5px
&, & > div.ace_editor
width: 100%
.treema-markdown.treema-display, .treema-coffee.treema-display, .treema-javascript.treema-display
width: 100%
border: 3px inset rgba(0, 100, 100, 0.2)
box-sizing: border-box
padding: 5px
.buttons button
float: left
margin-bottom: 5px
margin-right: 5px
.preview
clear: both
width: 100%
border: 3px inset rgba(0, 100, 100, 0.2)
box-sizing: border-box
padding: 5px
.treema-selection-map
position: fixed

View file

@ -5,7 +5,6 @@ div.editor-nano-container.nano
for group in groups
h4= group.name
for thangType in group.thangs
div.add-thang-palette-icon(data-thang-type=thangType.name)
- path = '/file/db/thang.type/'+thangType.original+'/portrait.png'
img(title="Add " + thangType.name, src=path, alt="")
div.add-thang-palette-icon(data-thang-type=thangType.get('name'))
img(title="Add " + thangType.get('name'), src=thangType.getPortraitURL(), alt="")
div.clearfix

View file

@ -12,14 +12,13 @@ table.table
th(data-i18n="general.description") Description
th(data-i18n="general.version") Version
for data in documents
- data = data.attributes;
for thang in documents
tr
td
- path = '/file/db/thang.type/'+data.original+'/portrait.png'
img(title="Add " + data.name, src=path, alt="").portrait
img(title="Add " + thang.get('name'), src=thang.getPortraitURL(), alt="").portrait
td
a(href="/editor/thang/#{data.slug || data._id}")
| #{data.name}
td.body-row #{data.description}
td #{data.version.major}.#{data.version.minor}
a(href="/editor/thang/#{thang.get('slug') || thang.id}")
| #{thang.get('name')}
td.body-row #{thang.get('description')}
- var version = thang.get('version')
td #{version.major}.#{version.minor}

View file

@ -11,7 +11,6 @@ block content
if level.get('name') == 'Greed'
.tournament-blurb
h2
//span(data-i18n="ladder.tournament_ends") Tournament ends
span(data-i18n="ladder.tournament_ended") Tournament ended
| #{tournamentTimeLeft}
p
@ -46,6 +45,17 @@ block content
a(href="http://aws.amazon.com/")
img(src=base + "aws.png")
if level.get('name') == 'Criss-Cross'
.tournament-blurb
h2
span(data-i18n="ladder.tournament_ends") Tournament ends
| #{tournamentTimeLeft}
p
span(data-i18n="ladder.tournament_blurb_criss_cross") Win bids, construct paths, outwit opponents, grab gems, and upgrade your career in our Criss-Cross tournament! Check out the details
|
a(href="http://blog.codecombat.com/6-programming-languages-one-victor-codecombats-newest-tournament", data-i18n="ladder.tournament_blurb_blog") on our blog
| .
div#columns.row
div.column.col-md-2
for team in teams
@ -73,8 +83,10 @@ block content
if level.get('name') == 'Greed'
li
a(href="#prizes", data-toggle="tab", data-i18n="ladder_prizes.prizes") Prizes
if level.get('name') == 'Greed'
li
a(href="#rules", data-toggle="tab", data-i18n="ladder.rules") Rules
if level.get('name') == 'Greed' || (level.get('name') == 'Criss-Cross!!!')
li
a(href="#winners", data-toggle="tab", data-i18n="ladder.winners") Winners
@ -651,6 +663,7 @@ block content
| - $50
td $50
if level.get('name') == 'Greed'
.tab-pane.well#rules
h1(data-i18n="ladder.tournament_rules") Tournament Rules
h2 General
@ -712,6 +725,7 @@ block content
a(href="http://discourse.codecombat.com/") Discourse forum
| .
if level.get('name') == 'Greed' || level.get('name') == 'Criss-Cross!!!'
.tab-pane.well#winners
h1(data-i18n="ladder.winners") Winners

View file

@ -8,8 +8,7 @@ div#columns.row
th(colspan=2)
th(colspan=3, style="color: #{team.primaryColor}")
span= team.name
span
span(data-i18n="ladder.leaderboard") Leaderboard
span.spl(data-i18n="ladder.leaderboard") Leaderboard
tr
th(colspan=2)
th(data-i18n="general.score") Score

View file

@ -4,7 +4,10 @@ h4.home
i.icon-home.icon-white
span(data-i18n="play_level.home") Home
h4.title #{worldName}
h4.title
| #{worldName}
| -
a(href=editorLink, data-i18n="nav.editor", title="Open " + worldName + " in the Level Editor") Editor
button.btn.btn-xs.btn-inverse.banner#game-menu-button(title="Show game menu", data-i18n="play_level.game_menu") Game Menu

View file

@ -16,7 +16,6 @@
strong.tip(data-i18n='play_level.tip_open_source') CodeCombat is 100% open source!
strong.tip(data-i18n='play_level.tip_beta_launch') CodeCombat launched its beta in October, 2013.
strong.tip(data-i18n='play_level.tip_js_beginning') JavaScript is just the beginning.
strong.tip(data-i18n='play_level.tip_autocast_setting') Adjust autocast settings by clicking the gear on the cast button.
strong.tip(data-i18n='play_level.tip_think_solution') Think of the solution, not the problem.
strong.tip(data-i18n='play_level.tip_theory_practice') In theory there is no difference between theory and practice; in practice there is. - Yogi Berra
strong.tip(data-i18n='play_level.tip_error_free') There are two ways to write error-free programs; only the third one works. - Alan Perlis

View file

@ -20,20 +20,30 @@ class LiveEditingMarkup extends TreemaNode.nodeMap.ace
super(arguments...)
@schema.aceMode = 'ace/mode/markdown'
buildValueForEditing: (valEl) ->
initEditor: (valEl) ->
buttonRow = $('<div class="buttons"></div>')
valEl.append(buttonRow)
@addPreviewToggle(buttonRow)
@addImageUpload(buttonRow)
super(valEl)
@editor.on('change', @onEditorChange)
@addImageUpload(valEl)
valEl.append($('<div class="preview"></div>'))
addImageUpload: (valEl) ->
return unless me.isAdmin()
valEl.append(
$('<div></div>').append(
$('<div class="pick-image-button"></div>').append(
$('<button>Pick Image</button>')
.addClass('btn btn-sm btn-primary')
.click(=> filepicker.pick @onFileChosen)
)
)
addPreviewToggle: (valEl) ->
valEl.append($('<div class="toggle-preview-button"></div>').append(
$('<button>Toggle Preview</button>')
.addClass('btn btn-sm btn-primary')
.click(@togglePreview)
))
onFileChosen: (InkBlob) =>
body =
@ -49,14 +59,19 @@ class LiveEditingMarkup extends TreemaNode.nodeMap.ace
onFileUploaded: (e) =>
@editor.insert "![#{e.metadata.name}](/file/#{@uploadingPath})"
onEditorChange: =>
@saveChanges()
@flushChanges()
@getRoot().broadcastChanges()
showingPreview: false
buildValueForDisplay: (valEl) ->
@editor?.destroy()
valEl.html(marked(@data))
togglePreview: =>
valEl = @getValEl()
if @showingPreview
valEl.find('.preview').hide()
valEl.find('.pick-image-button').show()
valEl.find('.ace_editor').show()
else
valEl.find('.preview').html(marked(@data)).show()
valEl.find('.pick-image-button').hide()
valEl.find('.ace_editor').hide()
@showingPreview = not @showingPreview
class SoundFileTreema extends TreemaNode.nodeMap.string
valueClass: 'treema-sound-file'

View file

@ -4,7 +4,7 @@ ThangType = require 'models/ThangType'
CocoCollection = require 'collections/CocoCollection'
class ThangTypeSearchCollection extends CocoCollection
url: '/db/thang.type?project=true'
url: '/db/thang.type?project=original,name,version,description,slug,kind,rasterIcon'
model: ThangType
addTerm: (term) ->
@ -33,18 +33,18 @@ module.exports = class AddThangsView extends CocoView
else
models = @supermodel.getModels(ThangType)
thangTypes = (thangType.attributes for thangType in models)
thangTypes = _.uniq thangTypes, false, 'original'
thangTypes = _.reject thangTypes, kind: 'Mark'
thangTypes = _.uniq models, false, (thangType) -> thangType.get('original')
thangTypes = _.reject thangTypes, (thangType) -> thangType.get('kind') is 'Mark'
groupMap = {}
for thangType in thangTypes
groupMap[thangType.kind] ?= []
groupMap[thangType.kind].push thangType
kind = thangType.get('kind')
groupMap[kind] ?= []
groupMap[kind].push thangType
groups = []
for groupName in Object.keys(groupMap).sort()
someThangTypes = groupMap[groupName]
someThangTypes = _.sortBy someThangTypes, 'name'
someThangTypes = _.sortBy someThangTypes, (thangType) -> thangType.get('name')
group =
name: groupName
thangs: someThangTypes

View file

@ -119,7 +119,7 @@ module.exports = class ThangsTabView extends CocoView
$('#thangs-list').bind 'mousewheel', @preventBodyScrollingInThangList
@$el.find('#extant-thangs-filter button:first').button('toggle')
$(window).resize @onWindowResize
@addThangsView = @insertSubView new AddThangsView world: @world, supermodel: @supermodel
@addThangsView = @insertSubView new AddThangsView world: @world
@buildInterface() # refactor to not have this trigger when this view re-renders?
if @thangsTreema.data.length
@$el.find('#canvas-overlay').css('display', 'none')

View file

@ -6,6 +6,7 @@ module.exports = class ThangTypeSearchView extends SearchView
model: require 'models/ThangType'
modelURL: '/db/thang.type'
tableTemplate: require 'templates/editor/thang/table'
projection: ['original', 'name', 'version', 'description', 'slug', 'kind', 'rasterIcon']
getRenderData: ->
context = super()

View file

@ -143,14 +143,14 @@ module.exports = class MainPlayView extends RootView
]
arenas = [
#{
# name: 'Criss-Cross'
# difficulty: 4
# id: 'criss-cross'
# image: '/file/db/level/528aea2d7f37fc4e0700016b/its_a_trap_icon.png'
# description: 'Participate in a bidding war with opponents to reach the other side!'
# levelPath: 'ladder'
#}
{
name: 'Criss-Cross'
difficulty: 5
id: 'criss-cross'
image: '/file/db/level/528aea2d7f37fc4e0700016b/its_a_trap_icon.png'
description: 'Participate in a bidding war with opponents to reach the other side!'
levelPath: 'ladder'
}
{
name: 'Greed'
difficulty: 4
@ -192,7 +192,7 @@ module.exports = class MainPlayView extends RootView
levelPath: 'ladder'
}
]
classicAlgorithms = [
{
name: 'Bubble Sort Bootcamp Battle'

View file

@ -52,13 +52,13 @@ module.exports = class LadderView extends RootView
ctx.levelID = @levelID
ctx.levelDescription = marked(@level.get('description')) if @level.get('description')
ctx._ = _
ctx.tournamentTimeLeft = moment(new Date(1402444800000)).fromNow()
if tournamentDate = {greed: 1402444800000, 'criss-cross': 1410912000000}[@levelID]
ctx.tournamentTimeLeft = moment(new Date(tournamentDate)).fromNow()
ctx.winners = require('views/play/ladder/tournament_results')[@levelID]
ctx
afterRender: ->
super()
# console.debug 'gintau', 'ladder_view-afterRender', @supermodel.finished()
return unless @supermodel.finished()
@insertSubView(@ladderTab = new LadderTabView({}, @level, @sessions))
@insertSubView(@myMatchesTab = new MyMatchesTabView({}, @level, @sessions))

View file

@ -30,6 +30,13 @@ module.exports = class LadderHomeView extends RootView
getRenderData: (context={}) ->
context = super(context)
arenas = [
{
name: 'Criss-Cross'
difficulty: 5
id: 'criss-cross'
image: '/file/db/level/5391f3d519dc22b8082159b2/banner2.png'
description: 'Participate in a bidding war with opponents to reach the other side!'
}
{
name: 'Greed'
difficulty: 4
@ -37,6 +44,13 @@ module.exports = class LadderHomeView extends RootView
image: '/file/db/level/53558b5a9914f5a90d7ccddb/greed_banner.jpg'
description: 'Liked Dungeon Arena and Gold Rush? Put them together in this economic arena!'
}
{
name: 'Sky Span (Testing)'
difficulty: 3
id: 'sky-span'
image: '/file/db/level/53c80fce0ddbef000084c667/sky-Span-banner.jpg'
description: 'Preview version of an upgraded Dungeon Arena. Help us with hero balance before release!'
}
{
name: 'Dungeon Arena'
difficulty: 3

View file

@ -18,9 +18,6 @@ module.exports = class SimulateTabView extends CocoView
@simulatorsLeaderboardDataRes = @supermodel.addModelResource(@simulatorsLeaderboardData, 'top_simulators')
@simulatorsLeaderboardDataRes.load()
@simulator = new Simulator()
@listenTo(@simulator, 'statusUpdate', @updateSimulationStatus)
onLoaded: ->
super()
@render()
@ -42,7 +39,21 @@ module.exports = class SimulateTabView extends CocoView
application.tracker?.trackEvent 'Simulate Button Click', {}
$('#simulate-button').prop 'disabled', true
$('#simulate-button').text 'Simulating...'
@simulateNextGame()
simulateNextGame: ->
unless @simulator
@simulator = new Simulator()
@listenTo @simulator, 'statusUpdate', @updateSimulationStatus
# Work around simulator getting super slow on Chrome
fetchAndSimulateTaskOriginal = @simulator.fetchAndSimulateTask
@simulator.fetchAndSimulateTask = =>
if @simulator.simulatedByYou >= 5
@simulator.destroy()
@simulator = null
@simulateNextGame()
else
fetchAndSimulateTaskOriginal.apply @simulator
@simulator.fetchAndSimulateTask()
refresh: ->
@ -51,20 +62,23 @@ module.exports = class SimulateTabView extends CocoView
$.ajax '/queue/messagesInQueueCount', {success}
updateSimulationStatus: (simulationStatus, sessions) ->
if simulationStatus is 'Fetching simulation data!'
@simulationMatchDescription = ''
@simulationSpectateLink = ''
@simulationStatus = simulationStatus
try
if sessions?
#TODO: Fetch names from Redis, the creatorName is denormalized
creatorNames = (session.creatorName for session in sessions)
@simulationStatus = 'Simulating game between '
for index in [0...creatorNames.length]
unless creatorNames[index]
creatorNames[index] = 'Anonymous'
@simulationStatus += (if index != 0 then ' and ' else '') + creatorNames[index]
@simulationStatus += '...'
@simulationMatchDescription = ''
@simulationSpectateLink = "/play/spectate/#{@simulator.level.get('slug')}?"
for session, index in sessions
# TODO: Fetch names from Redis, the creatorName is denormalized
@simulationMatchDescription += "#{if index then ' vs ' else ''}#{session.creatorName or 'Anonymous'} (#{sessions[index].team})"
@simulationSpectateLink += "session-#{if index then 'two' else 'one'}=#{session.sessionID}"
@simulationMatchDescription += " on #{@simulator.level.get('name')}"
catch e
console.log "There was a problem with the named simulation status: #{e}"
$('#simulation-status-text').text @simulationStatus
link = if @simulationSpectateLink then "<a href=#{@simulationSpectateLink}>#{_.string.escapeHTML(@simulationMatchDescription)}</a>" else ''
$('#simulation-status-text').html "<h3>#{@simulationStatus}</h3>#{link}"
resimulateAllSessions: ->
postData =
@ -81,7 +95,7 @@ module.exports = class SimulateTabView extends CocoView
destroy: ->
clearInterval @refreshInterval
@simulator.destroy()
@simulator?.destroy()
super()
class SimulatorsLeaderboardData extends CocoClass

View file

@ -54,6 +54,7 @@ module.exports = class ControlBarView extends CocoView
c.homeLink = '/play/ladder/' + @level.get('slug').replace /\-tutorial$/, ''
else
c.homeLink = '/'
c.editorLink = "/editor/level/#{@level.get('slug')}"
c
afterRender: ->

View file

@ -61,15 +61,16 @@ module.exports = class TomeView extends CocoView
super()
@worker = @createWorker()
programmableThangs = _.filter @options.thangs, 'isProgrammable'
if programmableThangs.length
@createSpells programmableThangs, programmableThangs[0].world # Do before spellList, thangList, and castButton
@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, levelID: @options.levelID
@teamSpellMap = @generateTeamSpellMap(@spells)
else
@createSpells programmableThangs, programmableThangs[0]?.world # Do before spellList, thangList, and castButton
@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, levelID: @options.levelID
@teamSpellMap = @generateTeamSpellMap(@spells)
unless programmableThangs.length
@cast()
console.warn 'Warning: There are no Programmable Thangs in this level, which makes it unplayable.'
warning = 'Warning: There are no Programmable Thangs in this level, which makes it unplayable.'
noty text: warning, layout: 'topCenter', type: 'warning', killer: false, timeout: 15000, dismissQueue: true, maxVisible: 3
console.warn warning
delete @options.thangs
onNewWorld: (e) ->

View file

@ -21,6 +21,7 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
'colorGroups'
'kind'
'raster'
'rasterIcon'
]
hasAccess: (req) ->