Added initial version of scoring system
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 25 Sep 2019 03:37:47 +0000 (22:37 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 25 Sep 2019 03:37:47 +0000 (22:37 -0500)
12 files changed:
codebox/app/app.moon
codebox/controllers/account/account.moon
codebox/controllers/account/login.moon
codebox/controllers/executer/status_update.moon
codebox/controllers/problem/problem.moon
codebox/facades/executer.moon
codebox/models/leaderboard_problems.moon
codebox/services/queries.moon
codebox/services/scoring.moon [new file with mode: 0644]
codebox/utils/inject.moon [new file with mode: 0644]
codebox/views/partials/navbar.moon
executer/app/executer.coffee

index 070af8a23122c130d89fcfea287c9d338b49f350..45949c0f27031ac6c7c1e3b25d018ce3e91ce85e 100644 (file)
@@ -1,15 +1,15 @@
 lapis = require "lapis"
 console = require "lapis.console"
 
-import Users, Competitions from require "models"
 bind = require "utils.binding"
-
 bind\bind_static 'executer', require 'facades.executer'
 bind\bind_static 'crypto', require 'services.crypto'
 bind\bind_static 'uuidv4', require 'services.uuid'
 bind\bind_static 'queries', require 'services.queries'
+bind\bind_static 'scoring', require 'services.scoring'
+bind\bind_static 'time', require 'utils.time'
 
--- Helper function that sppeds up requests by
+-- Helper function that speeds up requests by
 -- delaying the requiring of the controllers
 controller = (cont) ->
        return =>
@@ -35,7 +35,7 @@ class extends lapis.Application
        ['account.login':    "/login"]:    controller "account.login"
        ['account.logout':   "/logout"]:   controller "account.logout"
        ['account.register': "/register"]: controller "account.register"
-       ['account.account': "/account"]: controller "account.account"
+       ['account.account':  "/account"]:  controller "account.account"
 
        ['problem': '/problems']: controller "problem.problem"
        ['problem.description': '/problems/:problem_name']: controller "problem.problem"
index 9ec3e92a3984f616df289a205f33f6c62268dc8f..1751abd5d31b2b2c6eac656157b8dee2126ee927 100644 (file)
@@ -1,5 +1,4 @@
 import make_controller from require "controllers.controller"
-import Users from require 'models'
 import assert_valid from require "lapis.validate"
 import capture_errors, yield_error from require 'lapis.application'
 
@@ -23,7 +22,7 @@ make_controller
 
         if @user.username ~=@params.username
             yield_error 'You cannot change your username!'
-        
+
         @user\update
             nickname: @params.nickname
             email: @params.email
index a44204dd943c90c904749baabc50acb979b59ad5..bb26b1713e29738f42d57d8917755dc2c527e50a 100644 (file)
@@ -3,8 +3,6 @@ import Users from require 'models'
 import assert_valid from require "lapis.validate"
 import capture_errors, yield_error from require "lapis.application"
 
-utils = require "lapis.util"
-
 make_controller
        inject:
                crypto: 'crypto'
index 46e6b2d5faebcc29a517bc132bc07017c721092e..a5d231c8fa595d21e5451f525157b2a79291635f 100644 (file)
@@ -1,10 +1,13 @@
 import make_controller from require "controllers.controller"
-import Jobs from require 'models'
+import Jobs, Problems from require 'models'
 import from_json, to_json from require 'lapis.util'
 import assert_valid from require 'lapis.validate'
 import capture_errors, yield_error from require 'lapis.application'
 
 make_controller
+    inject:
+        scoring: 'scoring'
+
        middleware: { 'internal_request' }
 
        post: capture_errors (=>
@@ -24,8 +27,12 @@ make_controller
                        status: status.status
                        data: to_json status.data
                }
-       
-               print "Updated job: #{job.id}"
+
+        if status.status != Jobs.statuses.running
+            problem = Problems\find job.problem_id
+            @scoring\score_problem_for_user job.user_id, problem.short_name
+            @scoring\place!
+
                json: { status: 'success' }
        ), =>
                json: { status: 'error', errors: @errors }
index a38f71a66b8386724dcdce29f553764f551ee797..66f9090dabc79a1c5ea825f24640b6e4e89caa93 100644 (file)
@@ -1,8 +1,6 @@
 import make_controller from require "controllers.controller"
-import from_json, to_json from require 'lapis.util'
-import assert_valid from require 'lapis.validate'
-import capture_errors, capture_errors_json, yield_error from require 'lapis.application'
-import Competitions, Problems from require 'models'
+import capture_errors_json, yield_error from require 'lapis.application'
+import Problems from require 'models'
 
 make_controller
        inject:
index baa5b6d6e6d888a77bc973ca9eb2909cb748ae00..ff1bf5916be90faba559473361dd2847905a2cb3 100644 (file)
@@ -2,7 +2,6 @@ config = (require 'lapis.config').get!
 http = require 'lapis.nginx.http'
 
 import from_json, to_json from require 'lapis.util'
-import format_date from require 'lapis.db'
 import Jobs from require 'models'
 
 class ExecuterFacade
@@ -18,7 +17,7 @@ class ExecuterFacade
 
                job_id = from_json(body).id
 
-               job = Jobs\create {
+               Jobs\create {
                        job_id: job_id
                        user_id: user_id
                        problem_id: problem_id
@@ -30,4 +29,3 @@ class ExecuterFacade
                }
 
                job_id
-
index fd9704071d88b4e37173c26bc2d59df8507e72d3..b7d98b46087ea9b13d6bf642e18ba28da2ef31dc 100644 (file)
@@ -3,12 +3,13 @@ import Model, enum from require "lapis.db.model"
 class LeaderboardProblems extends Model
     @statuses: enum {
         not_attempted: 1
-        correct: 2
-        wrong: 3
+        attempted: 2
+        correct: 3
+        wrong: 4
     }
 
     @relations: {
         { "leaderboard_placement", belongs_to: 'LeaderboardPlacements' }
         { "user", belongs_to: 'Users' }
         { "problem", belongs_to: 'Problems' }
-    }
\ No newline at end of file
+    }
index c1ce23a67745a01d58bb43bc6b86bc7437629461..22a3a1617ad40200e1264bbc6759337f955bca4c 100644 (file)
@@ -2,17 +2,17 @@ db = require 'lapis.db'
 import Jobs from require 'models'
 
 has_correct_submission = (user_id, problem_name) ->
-    count = db.select "count(problems.id) from problems
-        inner join jobs on problems.id = jobs.problem_id
+    count = db.select "count(jobs.id) from jobs
+        inner join problems on problems.id = jobs.problem_id
         inner join competitions on jobs.competition_id=competitions.id
         where competitions.active=TRUE and jobs.status=? and jobs.user_id=? and problems.short_name=?
         ", (Jobs.statuses\for_db 'correct'), user_id, problem_name
-    
+
     return count[1].count > 0
 
 count_incorrect_submission = (user_id, problem_name) ->
-    count = db.select "count(problems.id) from problems
-        inner join jobs on problems.id = jobs.problem_id
+    count = db.select "count(jobs.id) from jobs
+        inner join problems on problems.id = jobs.problem_id
         inner join competitions on jobs.competition_id=competitions.id
         where competitions.active=TRUE and jobs.status in ? and jobs.user_id=? and problems.short_name=?
         ",
@@ -22,7 +22,7 @@ count_incorrect_submission = (user_id, problem_name) ->
             Jobs.statuses\for_db 'error'
             Jobs.statuses\for_db 'compile_err'
         }), user_id, problem_name
-    
+
     return count[1].count
 
 has_incorrect_submission = (user_id, problem_name) ->
@@ -31,9 +31,33 @@ has_incorrect_submission = (user_id, problem_name) ->
 get_jobs_by_user_and_problem_and_competition = (user_id, problem_id, competition_id) ->
     db.select "* from jobs where user_id=? and problem_id=? and competition_id=? order by time_initiated desc", user_id, problem_id, competition_id
 
+get_first_correct_submission = (user_id, problem_id, competition_id) ->
+    jobs = db.select "* from jobs where user_id=? and problem_id=? and competition_id=? order by time_initiated asc limit 1", user_id, problem_id, competition_id
+    jobs[1]
+
+delete_leaderboard_for_competition = (competition_id) ->
+    db.query "delete from leaderboard_problems
+        where leaderboard_placement_id in
+            (select id from leaderboard_placements where competition_id=?)", competition_id
+
+    db.delete "leaderboard_placements", competition_id: competition_id
+
+get_user_score = (user_id, competition_id) ->
+    res = db.select "sum(points)
+        from leaderboard_problems
+        inner join leaderboard_placements on leaderboard_placements.id=leaderboard_problems.leaderboard_placement_id
+        where leaderboard_placements.user_id=? and competition_id=?", user_id, competition_id
+
+    return 0 if #res == 0
+    res[1].sum
+
+
 -> {
-    :has_correct_submission,
-    :count_incorrect_submission,
+    :has_correct_submission
+    :count_incorrect_submission
     :has_incorrect_submission
     :get_jobs_by_user_and_problem_and_competition
-}
\ No newline at end of file
+    :get_first_correct_submission
+    :delete_leaderboard_for_competition
+    :get_user_score
+}
diff --git a/codebox/services/scoring.moon b/codebox/services/scoring.moon
new file mode 100644 (file)
index 0000000..c3ba9ab
--- /dev/null
@@ -0,0 +1,101 @@
+import Injectable from require 'utils.inject'
+import Users, Problems, Competitions, LeaderboardProblems, LeaderboardPlacements from require 'models'
+
+class Scoring extends Injectable
+       new: =>
+               @queries = @make 'queries'
+        @time = @make 'time'
+        @competition = Competitions\find active: true
+
+        @competition_start = @time.time_to_number @competition.start
+        @competition_end = @time.time_to_number @competition.end
+
+    setup_scoring_tables: =>
+        @queries.delete_leaderboard_for_competition @competition.id
+
+        users = Users\select!
+        problems = @competition\get_competition_problems!
+
+        for user in *users
+            placement = LeaderboardPlacements\create
+                competition_id: @competition.id
+                user_id: user.id
+
+            for problem in *problems
+                LeaderboardProblems\create
+                    leaderboard_placement_id: placement.id
+                    user_id: user.id
+                    problem_id: problem.problem_id
+
+    get_problem_worth: (time_submitted) =>
+        start = @competition_start + 30 * 60
+        return 1 if time_submitted < start
+        duration = @competition_end - start
+        percent = (time_submitted - start) / duration
+        1 - 0.5 * percent
+
+    score_problem_for_user: (user_id, problem_shortname) =>
+        placement = LeaderboardPlacements\find user_id: user_id, competition_id: @competition.id
+        status = LeaderboardProblems.statuses.not_attempted
+        points = 0
+        attempts = 0
+
+        problem = Problems\find short_name: problem_shortname
+
+        -- THIS SHOULD SWITCH ON PROBLEM KIND
+
+        attempts += @queries.count_incorrect_submission user_id, problem_shortname
+        if attempts > 0
+            status = LeaderboardProblems.statuses.wrong
+
+        if @queries.has_correct_submission user_id, problem_shortname
+            job = @queries.get_first_correct_submission user_id, problem.id, @competition.id
+
+            points += math.ceil (1000 * @get_problem_worth job.time_initiated)
+            points -= 50 * attempts
+            status = LeaderboardProblems.statuses.correct
+            attempts += 1
+
+        lp = LeaderboardProblems\find problem_id: problem.id, leaderboard_placement_id: placement.id
+        lp\update
+            status: status
+            points: points
+            attempts: attempts
+
+       score_user: (user_id) =>
+        problems = @competition\get_competition_problems!
+        for p in *problems
+            problem = p\get_problem!
+            @score_problem_for_user user_id, problem.short_name
+
+    score_problem: (problem_name) =>
+        users = Users\select!
+        for u in *users
+            @score_problem_for_user u.id, problem_name
+
+    score_all: =>
+        problems = @competition\get_competition_problems!
+        for p in *problems
+            problem = p\get_problem!
+            @score_problem problem.short_name
+
+    place: =>
+        users = Users\select!
+
+        for u in *users
+            u.score = @queries.get_user_score u.id, @competition.id
+
+        table.sort users, (a, b) ->
+            a.score - b.score
+
+        last_score = 1e308
+        num, act_num = 0, 0
+        for u in *users
+            act_num += 1
+            if last_score > u.score
+                num = act_num
+                last_score = u.score
+            lp = LeaderboardPlacements\find user_id: u.id, competition_id: @competition.id
+            lp\update
+                place: num
+                score: u.score
diff --git a/codebox/utils/inject.moon b/codebox/utils/inject.moon
new file mode 100644 (file)
index 0000000..6e3895a
--- /dev/null
@@ -0,0 +1,7 @@
+bind = require 'utils.binding'
+
+class Injectable
+    make: (thing) =>
+               bind\make thing
+
+{ :Injectable }
index 57fc3a33da96855d21234900c62bee48a70261ba..ccbb858fc187400fd42f38c065616058db2d1105 100644 (file)
@@ -22,6 +22,6 @@ class Navigation extends html.Widget
                                else
                                        a href: (@url_for 'account.login'), "Log in"
                                        div class: 'navbar-username-dropdown', ->
-                                               div class: 'navbar-username-dropdown-option', -> 
+                                               div class: 'navbar-username-dropdown-option', ->
                                                        a href: (@url_for 'account.register'), "Register"
 
index 7428023c1e0522a1c83382349e3bf95a0886d191..c1266faefcfca7c362678b4fd44b59e145cfe7c9 100644 (file)
@@ -77,14 +77,12 @@ class Executer
 
                        switch (res.status)
                                when 'SUCCESS'
-                                       console.log test_case.output, res.output
                                        output = clean_output res.output
                                        expected = create_matchers (clean_output test_case.output)
 
                                        worked = true
                                        i = 0
                                        for matcher in expected
-                                               console.log matcher.line, output[i]
                                                unless matcher.test output[i]
                                                        worked = false
                                                        break