From 4fcc86587e940017f3e5ba27d4b84572b07370b4 Mon Sep 17 00:00:00 2001 From: Patrick Quinn Date: Sat, 14 Jan 2012 01:32:17 -0500 Subject: [PATCH 1/4] Avoid refreshing turn logs when they're not visible Repeatedly running games takes about 1/3 less time when we use this optimization step. See Issue #23. --- web/multiLog.coffee | 6 ++++++ web/play.html | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/web/multiLog.coffee b/web/multiLog.coffee index 83dd0aa..fe39bee 100644 --- a/web/multiLog.coffee +++ b/web/multiLog.coffee @@ -21,6 +21,12 @@ class MultiLog getCurrent: -> @pages[@currentPage-1] + getCurrentPage: -> + @currentPage + + getLastPage: -> + @pages.length + updateOutput: -> if @pages[@currentPage-1]? @outputElt.html(@pages[@currentPage-1]) diff --git a/web/play.html b/web/play.html index 463bb41..33f4080 100644 --- a/web/play.html +++ b/web/play.html @@ -203,7 +203,8 @@

Game logs

singleGameResult = (state) => result = state.getWinners().toString() + " wins!" multiLog.addLineToEnd(result) - multiLog.updateOutput() + # Avoid refreshing the output if it's not visible + multiLog.updateOutput() if multiLog.getCurrentPage() == multiLog.getLastPage() tracker.recordGame(state) errorHandler = (error) => alert(error.message) From b456d46dce01a32e19ce13cd9ab489de16ca6afb Mon Sep 17 00:00:00 2001 From: Patrick Quinn Date: Thu, 19 Jan 2012 19:04:55 -0500 Subject: [PATCH 2/4] Avoid some unnecessary overhead in AI choice function. --- basicAI.coffee | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/basicAI.coffee b/basicAI.coffee index 2127661..a0bb0cf 100644 --- a/basicAI.coffee +++ b/basicAI.coffee @@ -82,13 +82,15 @@ class BasicAI choiceSet = {} for choice in choices choiceSet[choice] = choice + + nullable = null in choices # Get the priority list. - priority = priorityfunc.bind(this)(state, my) + priority = priorityfunc.call(this, state, my) # Now look up all the preferences in that list. The moment we encounter # a valid choice, we can return it. for preference in priority - if preference is null and null in choices + if preference is null and nullable return null if choiceSet[preference]? return choiceSet[preference] @@ -103,7 +105,7 @@ class BasicAI if (choice is null) or (choice is no) value = 0 else - value = valuefunc.bind(this)(state, choice, my) + value = valuefunc.call(this, state, choice, my) if value > bestValue bestValue = value bestChoice = choice From a3e7855816ee65f8c0bc980e8e3b1c4c960c9284 Mon Sep 17 00:00:00 2001 From: Patrick Quinn Date: Thu, 19 Jan 2012 20:16:47 -0500 Subject: [PATCH 3/4] Log the time spent playing --- web/play.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/web/play.html b/web/play.html index 33f4080..163e3bd 100644 --- a/web/play.html +++ b/web/play.html @@ -209,17 +209,21 @@

Game logs

errorHandler = (error) => alert(error.message) + startTime = null + begunPlaying = -> window.playing = true $('#playButton').removeClass('success').addClass('danger') $('#playButton').text('Stop playing') + startTime = new Date() - donePlaying = -> + donePlaying = (count) -> window.playing = false grapher.updateGraphs() tracker.updateScoresOnPage() $('#playButton').removeClass('danger').addClass('success') $('#playButton').text('Start playing') + console?.log?("Played #{count} games in #{(new Date() - startTime) / 1000.0}s"); window.donePlaying = donePlaying playOneGame = -> @@ -242,6 +246,7 @@

Game logs

playUntil = (condition, count) -> + startTime = new Date(); scripts = [leftEd.getValue(), rightEd.getValue()] compiled = compileStrategies(scripts, errorHandler) return if not compiled @@ -259,7 +264,7 @@

Game logs

playLoop = (result) -> singleGameResult(result) if condition() or (playCount >= count) or (not window.playing) - donePlaying() + donePlaying(playCount) else if (not options.fast) then multiLog.addPageQuietly('') nextIteration = -> From b51bf28122c68fa5d759e2a9e15f6defabedf151 Mon Sep 17 00:00:00 2001 From: Patrick Quinn Date: Thu, 19 Jan 2012 22:46:28 -0500 Subject: [PATCH 4/4] Turn slow mode into fast mode This fixes several major UI inefficiencies: * playWeb.coffee triggered output updates that were being suppressed by play.html I removed these. The output updates less frequently and is completely controlled by play.html. Eventually, all of the CoffeeScript in play.html should be merged with playWeb.coffee, but that's a different issue. * We were calling zeroTimeout very frequently. zeroTimeout is fast, but not free. We now call it approximately 4 times a second, enough to keep the browser from timing out, but not enough to cause noticeable performance overhead. * We had released control back to the browser based on the number of games played. We also updated the graphs based on the number of games played. Doing this way, we needed to be pessimistic about AI and browser performance. We now release control when a game is over and we've had control for more than .25s. * We were updating the DOM whenever we added a page to the log, even when we updated "quietly" because we updated the paginator. We now detect if the pagination has changed before updating it. Combined, these changes allow the "slow mode" simulations to run nearly as fast as the fast mode. In Firefox, the UI updates in slow mode cause about 10% runtime overhead. Because of this, I removed the fast mode checkbox. Fixes #23 --- playWeb.coffee | 27 --------------------------- web/multiLog.coffee | 10 +++++++++- web/play.html | 34 ++++++++++++---------------------- 3 files changed, 21 insertions(+), 50 deletions(-) diff --git a/playWeb.coffee b/playWeb.coffee index a04d984..5561a43 100644 --- a/playWeb.coffee +++ b/playWeb.coffee @@ -24,10 +24,6 @@ makeStrategy = (changes) -> ai[key] = value ai -# Setting `fast` to true will takesome shortcuts to play the game -# really quickly. These include -# producing no output, and not returning control to the browser between -# game steps. playGame = (strategies, options, ret) -> ais = (makeStrategy(item) for item in strategies) @@ -38,30 +34,7 @@ playGame = (strategies, options, ret) -> state = new State().setUpWithOptions(ais, options) ret ?= options.log - if options.fast - options.log = () -> - playFast(state, options, ret) - else - window.setZeroTimeout -> playStep(state, options, ret) -playStep = (state, options, ret) -> - if state.gameIsOver() - ret(state) - else - try - state.doPlay() - if state.phase == 'buy' and (not state.extraturn) and options.grapher? - options.grapher.recordMoney(state.current.ai.name, state.current.turnsTaken, state.current.coins) - if state.phase == 'cleanup' and (not state.extraturn) and options.grapher? - options.grapher.recordVP(state.current.ai.name, state.current.turnsTaken, state.current.getVP(state)) - window.setZeroTimeout -> playStep(state, options, ret) - catch err - errorHandler = options.errorHandler ? (alert ? console.log) - errorHandler(err.message) - window.donePlaying() - throw err - -playFast = (state, options, ret) -> until state.gameIsOver() try state.doPlay() diff --git a/web/multiLog.coffee b/web/multiLog.coffee index fe39bee..b8ea414 100644 --- a/web/multiLog.coffee +++ b/web/multiLog.coffee @@ -45,10 +45,16 @@ class MultiLog "next page" updatePaginator: -> + # Avoid updating the DOM if the new pages are the same as the old + pages = @pagesShown() + oldPages = @pagesRendered + if oldPages? and oldPages.length == pages.length and pages[0] == oldPages[0] + return if @currentPage == @renderedPage + prev = "
  • ← Previous
  • " next = "
  • Next →
  • " items = [prev] - for pageNum in this.pagesShown() + for pageNum in pages if pageNum == @currentPage item = "
  • #{pageNum}
  • " else @@ -57,6 +63,8 @@ class MultiLog items.push(next) @paginatorElt.html('
      ' + items.join('') + '
    ') this.updateEvents() + @pagesRendered = pages + @renderedPage = @currentPage updateEvents: -> $('.page').click (event) => diff --git a/web/play.html b/web/play.html index 163e3bd..0384504 100644 --- a/web/play.html +++ b/web/play.html @@ -91,13 +91,6 @@

    Player 2

    Include Colony and Platinum -
  • - -
  • Start playing @@ -236,7 +229,6 @@

    Game logs

    log: log colonies: $('#colonies').is(':checked') randomizeOrder: $('#randomize').is(':checked') - fast: $('#fast').is(':checked') tracker: tracker grapher: grapher } @@ -255,32 +247,30 @@

    Game logs

    log: log colonies: $('#colonies').is(':checked') randomizeOrder: $('#randomize').is(':checked') - fast: $('#fast').is(':checked') tracker: tracker grapher: grapher } playCount = 0 begunPlaying() + timeControlStarted = new Date() playLoop = (result) -> singleGameResult(result) if condition() or (playCount >= count) or (not window.playing) donePlaying(playCount) else - if (not options.fast) then multiLog.addPageQuietly('') - nextIteration = -> - playCount++ - playGame(compiled, options, playLoop) - if (not options.fast) and (playCount % 10 == 0) - grapher.updateGraphs() - if (not options.fast) or (playCount % 20 == 0) - tracker.updateScoresOnPage() - if (not options.fast) or (playCount % 10) == 0 - window.setZeroTimeout(nextIteration) + multiLog.addPageQuietly('') + msWithControl = new Date() - timeControlStarted + playCount++ + if msWithControl > 250 + tracker.updateScoresOnPage() + grapher.updateGraphs() + window.setZeroTimeout -> + timeControlStarted = new Date() + playGame(compiled, options, playLoop) else - # no time to stop and give control to the Web browser! - nextIteration() + playGame(compiled, options, playLoop) - if (not options.fast) then multiLog.addPageQuietly('') + multiLog.addPageQuietly('') playGame(compiled, options, playLoop) $('#playButton').click (event) ->