Improved branching choices and set up an A/B/C/D test around branching.

This commit is contained in:
Nick Winter 2014-10-23 16:36:59 -07:00
parent 83516f562b
commit 13fe1bbece
10 changed files with 84 additions and 21 deletions

View file

@ -202,10 +202,13 @@
victory_sign_up_poke: "Want to save your code? Create a free account!"
victory_rate_the_level: "Rate the level: " # Only in old-style levels.
victory_return_to_ladder: "Return to Ladder"
victory_play_next_level: "Play Next Level" # Only in old-style levels.
victory_play_continue: "Continue"
victory_play_more_practice: "More Practice"
victory_play_skip: "Skip Ahead"
victory_play_next_level: "Play Next Level"
victory_play_more_practice: "More Practice"
victory_play_too_easy: "Too Easy"
victory_play_just_right: "Just Right"
victory_play_too_hard: "Too Hard"
victory_saving_progress: "Saving Progress"
victory_go_home: "Go Home" # Only in old-style levels.
victory_review: "Tell us more!" # Only in old-style levels.

View file

@ -76,3 +76,15 @@ module.exports = class User extends CocoModel
earnedHero: (heroOriginal) -> heroOriginal in (me.get('earned')?.heroes ? [])
earnedItem: (itemOriginal) -> itemOriginal in (me.get('earned')?.items ? [])
earnedLevel: (levelOriginal) -> levelOriginal in (me.get('earned')?.levels ? [])
getBranchingGroup: ->
return @branchingGroup if @branchingGroup
group = me.get('testGroupNumber') % 4
@branchingGroup = switch group
when 0 then 'no-practice'
when 1 then 'all-practice'
when 2 then 'choice-explicit'
when 3 then 'choice-implicit'
@branchingGroup = 'choice-explicit' if me.isAdmin()
application.tracker.identify branchingGroup: @branchingGroup
@branchingGroup

View file

@ -202,6 +202,16 @@
.last-submitted
float: none
.next-levels-prompt
display: none
margin: 30px -21px
.btn
width: 30%
width: -webkit-calc(33.333333% - 10px)
width: calc(33.333333% - 10px)
margin: 5px
html.no-borderimage
#hero-victory-modal

View file

@ -41,6 +41,10 @@ block modal-body-content
img(src=item.getPortraitURL())
.reward-text= animate ? 'New Item' : item.get('name')
.next-levels-prompt
for button in continueButtons
- var enabled = Boolean(button.link != '/play' || me.getBranchingGroup() == 'choice-implicit' || button.key == 'continue');
a.btn.btn-success.btn-lg.world-map-button.next-level-branch-button(href=button.link, disabled=!enabled, data-dismiss="modal", data-i18n="play_level.victory_play_" + button[me.getBranchingGroup()], data-branch-key=button.key)
block modal-footer-content
if me.get('anonymous')
@ -63,8 +67,4 @@ block modal-footer-content
else if level.get('type') === 'hero-ladder'
a.btn.btn-primary(href="/play/ladder/#{level.get('slug')}#my-matches", data-dismiss="modal", data-i18n="play_level.victory_return_to_ladder") Return to Ladder
else
if morePracticeLevel
a.btn.btn-primary.world-map-button.next-level-button.hide#more-practice-button(href="/play?next=" + morePracticeLevel, data-dismiss="modal", data-i18n="play_level.victory_play_more_practice") More Practice
a.btn.btn-success.world-map-button.next-level-button.hide#continue-button(href="/play" + (continueLevel ? '?next=' + continueLevel : ''), data-dismiss="modal", data-i18n="play_level.victory_play_continue") Continue
if skipAheadLevel
a.btn.btn-primary.world-map-button.next-level-button.hide#skip-ahead-button(href="/play?next=" + skipAheadLevel, data-dismiss="modal", data-i18n="play_level.victory_skip_ahead") Skip Ahead
button.btn.btn-success.world-map-button.next-level-button.hide#continue-button(data-i18n="play_level.victory_play_continue") Continue

View file

@ -8,7 +8,9 @@
- var seenNext = nextLevel;
each campaign in campaigns
each level in campaign.levels
- var next = level.id == nextLevel || (!seenNext && levelStatusMap[level.id] != "complete");
if level.hidden
continue;
- var next = level.id == nextLevel || (!seenNext && levelStatusMap[level.id] != "complete" && !level.locked && !level.disabled);
- seenNext = seenNext || next;
div(style="left: #{level.x}%; bottom: #{level.y}%; background-color: #{level.color}", class="level" + (next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + levelStatusMap[level.id] || "", data-level-id=level.id, title=level.name)
a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.id}", disabled=level.disabled, data-level-id=level.id, data-level-path=level.levelPath || 'level', data-level-name=level.name)

View file

@ -86,7 +86,10 @@ module.exports = class WorldMapView extends RootView
level.locked = index > 0 and not me.earnedLevel level.original
window.levelUnlocksNotWorking = true if level.locked and level.id is @nextLevel # Temporary
level.locked = false if window.levelUnlocksNotWorking # Temporary; also possible in HeroVictoryModal
level.color = if level.practice then 'rgb(80, 130, 200)' else campaign.color
level.color = campaign.color
if level.practice
level.color = 'rgb(80, 130, 200)' unless me.getBranchingGroup() is 'all-practice'
level.hidden = true if me.getBranchingGroup() is 'no-practice'
context.levelStatusMap = @levelStatusMap
context.levelPlayCountMap = @levelPlayCountMap
context.isIPadApp = application.isIPadApp
@ -716,7 +719,7 @@ hero = [
skip_ahead: 'new-sight'
}
{
name: 'The One-Point_Fifth Kithmaze'
name: 'The One-Point-Fifth Kithmaze'
type: 'hero'
difficulty: 1
id: 'the-one-point-fifth-kithmaze'

View file

@ -18,6 +18,10 @@ module.exports = class HeroVictoryModal extends ModalView
subscriptions:
'ladder:game-submitted': 'onGameSubmitted'
events:
'click #continue-button': 'onClickContinue'
'click .next-level-branch-button': 'onClickNextLevelBranch'
constructor: (options) ->
super(options)
@session = options.session
@ -33,6 +37,10 @@ module.exports = class HeroVictoryModal extends ModalView
@waitingToContinueSince = new Date()
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'victory'
destroy: ->
clearInterval @sequentialAnimationInterval
super()
onAchievementsLoaded: ->
thangTypeOriginals = []
achievementIDs = []
@ -102,9 +110,14 @@ module.exports = class HeroVictoryModal extends ModalView
c.me = me
c.readyToRank = @level.get('type', true) is 'hero-ladder' and @session.readyToRank()
c.level = @level
c.continueLevel = @getNextLevel 'continue'
c.morePracticeLevel = me.isAdmin() and @getNextLevel 'more_practice'
c.skipAheadLevel = me.isAdmin() and @getNextLevel 'skip_ahead'
@continueLevelLink = @getNextLevelLink 'continue'
@morePracticeLevelLink = me.isAdmin() and @getNextLevelLink 'more_practice'
@skipAheadLevelLink = me.isAdmin() and @getNextLevelLink 'skip_ahead'
c.continueButtons = [
{key: 'skip_ahead', link: @skipAheadLevelLink, 'choice-explicit': 'skip', 'choice-implicit': 'too_easy'}
{key: 'continue', link: @continueLevelLink, 'choice-explicit': 'next_level', 'choice-implicit': 'just_right'}
{key: 'more_practice', link: @morePracticeLevelLink, 'choice-explicit': 'more_practice', 'choice-implicit': 'too_hard'}
]
return c
afterRender: ->
@ -227,7 +240,8 @@ module.exports = class HeroVictoryModal extends ModalView
onGameSubmitted: (e) ->
ladderURL = "/play/ladder/#{@level.get('slug')}#my-matches"
Backbone.Mediator.publish 'router:navigate', route: ladderURL
# Preserve the supermodel as we navigate back to the ladder.
Backbone.Mediator.publish 'router:navigate', route: ladderURL, viewClass: require('views/play/ladder/LadderView'), viewArgs: [{supermodel: @supermodel}]
playSelectionSound: (hero, preload=false) ->
return unless sounds = hero.get('soundTriggers')?.selected
@ -238,6 +252,8 @@ module.exports = class HeroVictoryModal extends ModalView
else
AudioPlayer.playSound name, 1
# Branching group testing
getNextLevel: (type) ->
for campaign in require('views/play/WorldMapView').campaigns
break if levelInfo
@ -247,8 +263,25 @@ module.exports = class HeroVictoryModal extends ModalView
break
levelInfo?.nextLevels?[type] # 'more_practice', 'skip_ahead', 'continue'
# TODO: award heroes/items and play an awesome sound when you get one
getNextLevelLink: (type) ->
return '/play' unless nextLevel = @getNextLevel type
"play?next=#{nextLevel}"
destroy: ->
clearInterval @sequentialAnimationInterval
super()
onClickContinue: (e) ->
nextLevelLink = @continueLevelLink
if me.getBranchingGroup() is 'all-practice' and @morePracticeLevelLink
nextLevelLink = @morePracticeLevelLink
skipPrompt = me.getBranchingGroup() in ['no-practice', 'all-practice']
skipPrompt ||= not (@skipAheadLevelLink or @morePractiveLevelLink) and me.getBranchingGroup() is 'choice-explicit'
if skipPrompt
# Preserve the supermodel as we navigate back to the world map.
Backbone.Mediator.publish 'router:navigate', route: nextLevelLink, viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: @supermodel}]
else
# Hide everything except the buttons prompting them for which kind of next level to do
@$el.find('.modal-footer, .modal-body > *').hide()
@$el.find('.next-levels-prompt').show()
onClickNextLevelBranch: (e) ->
application.tracker?.trackEvent 'Branch Selected', level: @level.get('slug'), label: @level.get('slug'), branch: $(e.target).data('branch-key'), branchingGroup: me.getBranchingGroup()
# Preserve the supermodel as we navigate back to world map.
Backbone.Mediator.publish 'router:navigate', route: '/play', viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: @supermodel}]

View file

@ -75,7 +75,7 @@ module.exports = class SpellPaletteView extends CocoView
columns = ({items: [], nEntries: 0} for i in [0 ... nColumns])
nRows = 0
for group, entries of @entryGroups
shortestColumn = _.sortBy(columns, (column) -> column.nEntries)[0]
continue unless shortestColumn = _.sortBy(columns, (column) -> column.nEntries)[0]
shortestColumn.nEntries += Math.max 2, entries.length
shortestColumn.items.push @entryGroupElements[group]
nRows = Math.max nRows, shortestColumn.nEntries

View file

@ -641,7 +641,7 @@ module.exports = class SpellView extends CocoView
# TODO: move this whole thing into SpellDebugView or somewhere?
@highlightComments() unless @destroyed
flow ?= @spellThang?.castAether?.flow
return unless flow
return unless flow and @thang
executed = []
executedRows = {}
matched = false

View file

@ -32,7 +32,7 @@ setupExpressMiddleware = (app) ->
express.logger.format('prod', productionLogging)
app.use(express.logger('prod'))
app.use express.compress filter: (req, res) ->
return false if req.headers.host is 'codecombat.com' # Cloudflare will gzip it for us on codecombat.com
#return false if req.headers.host is 'codecombat.com' # CloudFlare will gzip it for us on codecombat.com # But now it's disabled.
compressible res.getHeader('Content-Type')
else
app.use(express.logger('dev'))