From 4db4e65ee1948527358845bbeb57625a2b7eae4b Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Tue, 24 Sep 2019 22:37:47 -0500 Subject: [PATCH] Added initial version of scoring system --- codebox/app/app.moon | 8 +- codebox/controllers/account/account.moon | 3 +- codebox/controllers/account/login.moon | 2 - .../controllers/executer/status_update.moon | 13 ++- codebox/controllers/problem/problem.moon | 6 +- codebox/facades/executer.moon | 4 +- codebox/models/leaderboard_problems.moon | 7 +- codebox/services/queries.moon | 42 ++++++-- codebox/services/scoring.moon | 101 ++++++++++++++++++ codebox/utils/inject.moon | 7 ++ codebox/views/partials/navbar.moon | 2 +- executer/app/executer.coffee | 2 - 12 files changed, 164 insertions(+), 33 deletions(-) create mode 100644 codebox/services/scoring.moon create mode 100644 codebox/utils/inject.moon diff --git a/codebox/app/app.moon b/codebox/app/app.moon index 070af8a..45949c0 100644 --- a/codebox/app/app.moon +++ b/codebox/app/app.moon @@ -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" diff --git a/codebox/controllers/account/account.moon b/codebox/controllers/account/account.moon index 9ec3e92..1751abd 100644 --- a/codebox/controllers/account/account.moon +++ b/codebox/controllers/account/account.moon @@ -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 diff --git a/codebox/controllers/account/login.moon b/codebox/controllers/account/login.moon index a44204d..bb26b17 100644 --- a/codebox/controllers/account/login.moon +++ b/codebox/controllers/account/login.moon @@ -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' diff --git a/codebox/controllers/executer/status_update.moon b/codebox/controllers/executer/status_update.moon index 46e6b2d..a5d231c 100644 --- a/codebox/controllers/executer/status_update.moon +++ b/codebox/controllers/executer/status_update.moon @@ -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 } diff --git a/codebox/controllers/problem/problem.moon b/codebox/controllers/problem/problem.moon index a38f71a..66f9090 100644 --- a/codebox/controllers/problem/problem.moon +++ b/codebox/controllers/problem/problem.moon @@ -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: diff --git a/codebox/facades/executer.moon b/codebox/facades/executer.moon index baa5b6d..ff1bf59 100644 --- a/codebox/facades/executer.moon +++ b/codebox/facades/executer.moon @@ -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 - diff --git a/codebox/models/leaderboard_problems.moon b/codebox/models/leaderboard_problems.moon index fd97040..b7d98b4 100644 --- a/codebox/models/leaderboard_problems.moon +++ b/codebox/models/leaderboard_problems.moon @@ -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 + } diff --git a/codebox/services/queries.moon b/codebox/services/queries.moon index c1ce23a..22a3a16 100644 --- a/codebox/services/queries.moon +++ b/codebox/services/queries.moon @@ -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 index 0000000..c3ba9ab --- /dev/null +++ b/codebox/services/scoring.moon @@ -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 index 0000000..6e3895a --- /dev/null +++ b/codebox/utils/inject.moon @@ -0,0 +1,7 @@ +bind = require 'utils.binding' + +class Injectable + make: (thing) => + bind\make thing + +{ :Injectable } diff --git a/codebox/views/partials/navbar.moon b/codebox/views/partials/navbar.moon index 57fc3a3..ccbb858 100644 --- a/codebox/views/partials/navbar.moon +++ b/codebox/views/partials/navbar.moon @@ -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" diff --git a/executer/app/executer.coffee b/executer/app/executer.coffee index 7428023..c1266fa 100644 --- a/executer/app/executer.coffee +++ b/executer/app/executer.coffee @@ -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 -- 2.25.1