summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/images/Plus.pngbin0 -> 205 bytes
-rw-r--r--app/assets/images/bg.pngbin0 -> 2725 bytes
-rw-r--r--app/assets/images/chess.pngbin0 -> 893 bytes
-rw-r--r--app/assets/images/hearthstone.pngbin0 -> 10229 bytes
-rw-r--r--app/assets/images/league_of_legends.pngbin0 -> 10078 bytes
-rw-r--r--app/assets/images/rock_paper_scissors.pngbin0 -> 4951 bytes
-rw-r--r--app/assets/images/rock_paper_scissors.svg108
-rw-r--r--app/assets/javascripts/alerts/edit.js.coffee0
-rw-r--r--app/assets/javascripts/alerts/index.js.coffee0
-rw-r--r--app/assets/javascripts/alerts/new.js.coffee0
-rw-r--r--app/assets/javascripts/alerts/show.js.coffee0
-rw-r--r--app/assets/javascripts/application.js6
-rw-r--r--app/assets/javascripts/application/.keep0
-rw-r--r--app/assets/javascripts/application/layout.js.coffee21
-rw-r--r--app/assets/javascripts/brackets/edit.js.coffee0
-rw-r--r--app/assets/javascripts/brackets/index.js.coffee0
-rw-r--r--app/assets/javascripts/brackets/new.js.coffee0
-rw-r--r--app/assets/javascripts/brackets/show.js.coffee0
-rw-r--r--app/assets/javascripts/games/edit.js.coffee0
-rw-r--r--app/assets/javascripts/games/index.js.coffee0
-rw-r--r--app/assets/javascripts/games/new.js.coffee0
-rw-r--r--app/assets/javascripts/games/show.js.coffee0
-rw-r--r--app/assets/javascripts/main/homepage.js.coffee0
-rw-r--r--app/assets/javascripts/matches/show.js.coffee2
-rw-r--r--app/assets/javascripts/pms/edit.js.coffee0
-rw-r--r--app/assets/javascripts/pms/index.js.coffee0
-rw-r--r--app/assets/javascripts/pms/new.js.coffee0
-rw-r--r--app/assets/javascripts/pms/show.js.coffee25
-rw-r--r--app/assets/javascripts/search/go.js.coffee0
-rw-r--r--app/assets/javascripts/server/edit.js.coffee0
-rw-r--r--app/assets/javascripts/server/show.js.coffee0
-rw-r--r--app/assets/javascripts/sessions/new.js.coffee0
-rw-r--r--app/assets/javascripts/teams/edit.js.coffee0
-rw-r--r--app/assets/javascripts/teams/index.js.coffee0
-rw-r--r--app/assets/javascripts/teams/new.js.coffee0
-rw-r--r--app/assets/javascripts/teams/show.js.coffee0
-rw-r--r--app/assets/javascripts/tournaments/edit.js.coffee0
-rw-r--r--app/assets/javascripts/tournaments/index.js.coffee0
-rw-r--r--app/assets/javascripts/tournaments/new.js.coffee0
-rw-r--r--app/assets/javascripts/tournaments/show.js.coffee32
-rw-r--r--app/assets/javascripts/users/edit.js.coffee0
-rw-r--r--app/assets/javascripts/users/index.js.coffee0
-rw-r--r--app/assets/javascripts/users/new.js.coffee0
-rw-r--r--app/assets/javascripts/users/show.js.coffee0
-rw-r--r--app/assets/stylesheets/alerts.css.scss3
-rw-r--r--app/assets/stylesheets/application.css7
-rw-r--r--app/assets/stylesheets/application/scaffolds.css.scss329
-rw-r--r--app/assets/stylesheets/brackets.css.scss3
-rw-r--r--app/assets/stylesheets/colors.css.scss5
-rw-r--r--app/assets/stylesheets/games.css.scss3
-rw-r--r--app/assets/stylesheets/main.css.scss10
-rw-r--r--app/assets/stylesheets/matches.css.scss39
-rw-r--r--app/assets/stylesheets/patch54
-rw-r--r--app/assets/stylesheets/pms.css.scss3
-rw-r--r--app/assets/stylesheets/scaffolds.css.scss69
-rw-r--r--app/assets/stylesheets/search.css.scss3
-rw-r--r--app/assets/stylesheets/servers.css.scss3
-rw-r--r--app/assets/stylesheets/sessions.css.scss3
-rw-r--r--app/assets/stylesheets/teams.css.scss3
-rw-r--r--app/assets/stylesheets/tournaments.css.scss44
-rw-r--r--app/assets/stylesheets/users.css.scss3
-rw-r--r--app/controllers/alerts_controller.rb14
-rw-r--r--app/controllers/application_controller.rb50
-rw-r--r--app/controllers/brackets_controller.rb60
-rw-r--r--app/controllers/games_controller.rb2
-rw-r--r--app/controllers/main_controller.rb2
-rw-r--r--app/controllers/matches_controller.rb121
-rw-r--r--app/controllers/pms_controller.rb13
-rw-r--r--app/controllers/search_controller.rb39
-rw-r--r--app/controllers/servers_controller.rb54
-rw-r--r--app/controllers/sessions_controller.rb61
-rw-r--r--app/controllers/teams_controller.rb5
-rw-r--r--app/controllers/tournaments_controller.rb178
-rw-r--r--app/controllers/users_controller.rb44
-rw-r--r--app/helpers/sessions_helper.rb65
-rw-r--r--app/models/alert.rb2
-rw-r--r--app/models/bracket.rb28
-rw-r--r--app/models/bracket_match.rb4
-rw-r--r--app/models/game.rb44
-rw-r--r--app/models/game_setting.rb24
-rw-r--r--app/models/match.rb118
-rw-r--r--app/models/pm.rb14
-rw-r--r--app/models/remote_username.rb14
-rw-r--r--app/models/server.rb36
-rw-r--r--app/models/session.rb39
-rw-r--r--app/models/statistic.rb23
-rw-r--r--app/models/team.rb4
-rw-r--r--app/models/tournament.rb177
-rw-r--r--app/models/tournament_setting.rb16
-rw-r--r--app/models/tournament_stage.rb48
-rw-r--r--app/models/user.rb206
-rw-r--r--app/views/alerts/_form.html.erb5
-rw-r--r--app/views/alerts/index.html.erb20
-rw-r--r--app/views/alerts/new.html.erb2
-rw-r--r--app/views/alerts/show.html.erb4
-rw-r--r--app/views/application/.keep0
-rw-r--r--app/views/brackets/index.html.erb7
-rw-r--r--app/views/brackets/new.html.erb4
-rw-r--r--app/views/brackets/show.html.erb121
-rw-r--r--app/views/common/_error_messages.html.erb11
-rw-r--r--app/views/common/_show_tournament.html.erb49
-rw-r--r--app/views/common/_show_user.html.erb24
-rw-r--r--app/views/games/index.html.erb6
-rw-r--r--app/views/games/show.html.erb2
-rw-r--r--app/views/layouts/application.html.erb60
-rw-r--r--app/views/main/homepage.html.erb18
-rw-r--r--app/views/matches/_form.html.erb15
-rw-r--r--app/views/matches/index.html.erb59
-rw-r--r--app/views/matches/new.html.erb2
-rw-r--r--app/views/matches/show.html.erb82
-rw-r--r--app/views/pms/_form.html.erb16
-rw-r--r--app/views/pms/index.html.erb150
-rw-r--r--app/views/pms/show.html.erb55
-rw-r--r--app/views/search/go.html.erb45
-rw-r--r--app/views/servers/_form.html.erb29
-rw-r--r--app/views/servers/edit.html.erb3
-rw-r--r--app/views/servers/index.html.erb27
-rw-r--r--app/views/servers/index.json.jbuilder4
-rw-r--r--app/views/servers/new.html.erb5
-rw-r--r--app/views/servers/show.html.erb5
-rw-r--r--app/views/sessions/_form.html.erb25
-rw-r--r--app/views/sessions/edit.html.erb6
-rw-r--r--app/views/sessions/index.html.erb29
-rw-r--r--app/views/sessions/index.json.jbuilder4
-rw-r--r--app/views/sessions/new.html.erb24
-rw-r--r--app/views/sessions/show.html.erb14
-rw-r--r--app/views/sessions/show.json.jbuilder1
-rw-r--r--app/views/teams/show.html.erb2
-rw-r--r--app/views/tournaments/_form.html.erb155
-rw-r--r--app/views/tournaments/index.html.erb57
-rw-r--r--app/views/tournaments/join.html.erb2
-rw-r--r--app/views/tournaments/new.html.erb2
-rw-r--r--app/views/tournaments/show.html.erb146
-rw-r--r--app/views/tournaments/standings.html.erb28
-rw-r--r--app/views/users/_form.html.erb40
-rw-r--r--app/views/users/already_signed_in.html.erb1
-rw-r--r--app/views/users/edit.html.erb2
-rw-r--r--app/views/users/index.html.erb6
-rw-r--r--app/views/users/new.html.erb35
-rw-r--r--app/views/users/show.html.erb48
-rw-r--r--blue.yaml200
-rw-r--r--config/application.example.yml3
-rw-r--r--config/application.rb3
-rw-r--r--config/initializers/mailboxer.rb2
-rw-r--r--config/initializers/secret_token.rb6
-rw-r--r--config/locales/en.yml8
-rw-r--r--config/routes.rb34
-rwxr-xr-xdb.sh2
-rw-r--r--db/seeds.rb198
-rw-r--r--doc/Sprint1-Retrospective.md220
-rw-r--r--doc/Sprint2-Retrospective.md215
-rw-r--r--doc/Sprint2.md103
-rw-r--r--doc/Sprint3.md80
-rw-r--r--doc/sprint2Retro.md12
-rw-r--r--lib/sampling/README.md50
-rw-r--r--lib/sampling/double_blind.rb.bak35
-rw-r--r--lib/sampling/manual.html.erb13
-rw-r--r--lib/sampling/manual.rb53
-rw-r--r--lib/sampling/peer_review.html.erb28
-rw-r--r--lib/sampling/peer_review.rb91
-rw-r--r--lib/sampling/riot_api.rb207
-rw-r--r--lib/scheduling/README.md22
-rw-r--r--lib/scheduling/elimination.rb145
-rw-r--r--lib/scheduling/round_robin.rb70
-rw-r--r--lib/scoring/README.md15
-rw-r--r--lib/scoring/fibonacci_peer_with_blowout.rb28
-rw-r--r--lib/scoring/marginal_peer.rb15
-rw-r--r--lib/scoring/winner_takes_all.rb20
-rw-r--r--lib/seeding/.keep0
-rw-r--r--lib/seeding/README.md10
-rw-r--r--lib/seeding/early_bird_seeding.rb20
-rw-r--r--lib/seeding/fair_ranked_seeding.rb43
-rw-r--r--lib/seeding/random_seeding.rb20
-rw-r--r--lib/throttled_api_request.rb32
-rw-r--r--purple.yaml194
-rwxr-xr-xstart11
-rwxr-xr-xstop.sh3
-rw-r--r--vendor/assets/javascripts/coordinates.js94
-rw-r--r--vendor/assets/javascripts/drag.js229
-rw-r--r--vendor/assets/javascripts/dragsort.js234
180 files changed, 5763 insertions, 745 deletions
diff --git a/app/assets/images/Plus.png b/app/assets/images/Plus.png
new file mode 100644
index 0000000..fa6a7a5
--- /dev/null
+++ b/app/assets/images/Plus.png
Binary files differ
diff --git a/app/assets/images/bg.png b/app/assets/images/bg.png
new file mode 100644
index 0000000..91c77c8
--- /dev/null
+++ b/app/assets/images/bg.png
Binary files differ
diff --git a/app/assets/images/chess.png b/app/assets/images/chess.png
new file mode 100644
index 0000000..6bcffe6
--- /dev/null
+++ b/app/assets/images/chess.png
Binary files differ
diff --git a/app/assets/images/hearthstone.png b/app/assets/images/hearthstone.png
new file mode 100644
index 0000000..15d20b4
--- /dev/null
+++ b/app/assets/images/hearthstone.png
Binary files differ
diff --git a/app/assets/images/league_of_legends.png b/app/assets/images/league_of_legends.png
new file mode 100644
index 0000000..9a78047
--- /dev/null
+++ b/app/assets/images/league_of_legends.png
Binary files differ
diff --git a/app/assets/images/rock_paper_scissors.png b/app/assets/images/rock_paper_scissors.png
new file mode 100644
index 0000000..294916c
--- /dev/null
+++ b/app/assets/images/rock_paper_scissors.png
Binary files differ
diff --git a/app/assets/images/rock_paper_scissors.svg b/app/assets/images/rock_paper_scissors.svg
new file mode 100644
index 0000000..67bb6bd
--- /dev/null
+++ b/app/assets/images/rock_paper_scissors.svg
@@ -0,0 +1,108 @@
+<?xml version="1.0"?>
+<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:svg="http://www.w3.org/2000/svg" id="svg2" viewBox="0 0 691.81 691.81" version="1.1">
+ <title id="title4919">Rock Scissors Paper</title>
+ <defs id="defs4">
+ <marker id="Arrow2Mend" refY="0" refX="0" overflow="visible" orient="auto">
+ <path id="path4038" stroke-linejoin="round" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill-rule="evenodd" transform="scale(-.6)" stroke-width=".625"/>
+ </marker>
+ </defs>
+ <rect x="0" y="0" width="691.81" height="691.81" style="fill: #AAAAAA" />
+ <g id="layer1" transform="translate(-42.612 -137.16)">
+ <g id="g4875" transform="matrix(.77244 0 0 .77244 192.28 -14.824)">
+ <g id="g2906" transform="matrix(-.84212 .17678 .17678 .84212 677.84 268.6)">
+ <path id="path3" d="m259.08 17.719c-4.01-0.256-2.042 5.467-6.258 5.006-2.182-2.162-1.385-7.737 1.251-8.761 0.281 0.971 0.035 2.469 1.252 2.503 0.28-0.97 0.034-2.468 1.251-2.503 0.299 1.786 2.062 2.111 2.504 3.755z"/>
+ <path id="path5" d="m173.97 17.719c-1.584 1.436-9.868 2.515-11.265 0 4.306 0.479 7.803-6.286 11.265-2.503-1.511 0.264-0.771 1.389 0 2.503z"/>
+ <path id="path7" d="m240.3 20.221c-1.901 1.276-8.113-1.155-10.014-3.754 5.03-0.44 9.158 0.02 10.014 3.754z"/>
+ <path id="path9" d="m290.36 57.769c1.164-2.922 4.529-10.066 0-5.006-1.437-2.351-0.207-10.399 5.006-10.013 0.505 7.179 0.247 13.597-5.006 15.019z"/>
+ <path id="path11" d="m112.88 15.406h-18.773c-0.743-0.873 29.706-4.351 18.773 0z"/>
+ <path id="path13" d="m369.21 86.556c-0.326-1.948-2.745-0.868-3.754 0 3.313-2.11 1.396-9.449 3.754-12.516 1.525 1.894 2.458 10.78 0 12.516z"/>
+ <path id="path15" d="m295.37 89.058c1.109-4.731 0.937-10.745 3.756-13.767-0.142 5.699-0.024 11.657-3.756 13.767z"/>
+ <path id="path17" d="m191.49 85.303c-7.078-0.848-17.994 2.142-21.276-2.503 9.66 0.83 16.427-3.137 21.276 2.503z"/>
+ <path id="path19" d="m334.17 125.35c-1.795-1.543-2.371-4.304-2.503-7.51 2.142-1.195 2.521-4.152 5.006-5.005-1.2 3.807 2.68 12.692-2.503 12.515z"/>
+ <path id="path21" d="m289.11 124.1c0.39-3.784-1.39-9.734 2.503-10.013-1.03 3.141 2.434 10.775-2.503 10.013z"/>
+ <path id="path23" d="m188.99 126.61c-4.574 0.851-9.1 1.746-15.019 1.251 1.243-3.813 9.789-6.836 13.769-3.754-7.763 0.486 0.084 0.641 1.25 2.503z"/>
+ <path id="path25" d="m200.25 169.16c-0.073-5.496 0.504-10.345 2.503-13.768-1.261 4.163 2.766 13.615-2.503 13.768z"/>
+ <path id="path27" d="m158.95 46.505c5.797 0.809 10.295-4.935 15.019 0-5.767-1.534-11.103 3.833-15.019 0z"/>
+ <path id="path29" d="m46.229 20.57c8.204 0.277 14.635-1.219 21.277-2.503-2.471 4.077-20.887 4.58-21.277 2.503z"/>
+ <path id="path31" d="m281.6 126.61c1.312-2.515 0.599-13.491 2.503-11.265 0.411 4.236-0.976 13.112-2.503 11.265z"/>
+ <path id="path33" d="m285.36 129.11c0.745-6.283 0.367-10.093 1.252-13.768 2.044 2.957 2.173 12.365-1.252 13.768z"/>
+ <path id="path35" d="m367.96 126.6c-5.002 1.868-15.024 1.868-20.025 0-1.197-7.076 1.841-16.464 0-20.024-46.83 1.298-86.581 1.938-135.17 1.251 20.958-7.99 48.129-3.82 75.094-5.007 43.017-1.89 87.378 1.49 122.65-7.509 4.33-5.413 3.127-19.587-2.503-22.527 2.882 6.014 0.101 13.255-2.503 17.521-8.105 0.18-16.143 0.289-21.276-2.502 0.216-7.711-1.787-17.64 5.006-18.773-31.898 1.353-56.328-3.795-86.357-1.252 0.331 5.869 3.998 16.742-2.503 18.773 1.253-5.3 2.223-13.686 0-18.773-29.323-3.635-65.038-0.877-91.364-7.51 62.248-1.654 128.88 7.685 183.98 1.252-0.668-4.338 1.859-11.87-2.503-12.516 0.728 4.898-1.077 7.267-2.503 10.013-8.161-0.962-16.246 1.611-21.275 0 0.783-3.804-1.603-10.78 1.252-12.516-5.466 15.413 17.483 12.443 20.023 5.006-6.653-8.223-20.667-9.609-32.541-11.264-47.229-6.583-111.25 0.445-155.19-10.013 36.4 2.273 86.049 1.268 121.4 2.503 7.167 0.251 22.518 6.253 21.276-7.509-3.388 4.539-11.951 3.901-20.025 3.754-2.494-1.276 2.634-8.914-1.251-11.264-51.09-5.314-113.91-14.316-167.7-11.256-41.5 2.359-82.498-0.122-117.64 5.006-12.469 1.817-24.626 7.461-36.298 5.006 22.245-7.795 47.513-9.995 75.093-11.263 32.795-1.507 69.275-6.218 101.38-5.007 6.743 0.256 13.555 3.074 20.025 3.756 52.454 5.521 106.54 1.751 148.94 17.521 3.519 3.539 1.673 11.946 2.503 15.02 22.246 3.101 56.888-0.582 48.811 28.785 5.949-0.524 9.027 1.819 13.769 2.503 4.653 6.229 8.617 18.292 5.005 28.787-7.016 9.671-28.406 4.97-41.302 8.762 7.52 10.533 1.729 26.649-6.257 32.54-31.736 6.23-69.292 6.638-106.38 7.51 5.988 5.631 10.071 17.214 6.259 28.786-27.447 16.401-68.675 7.55-100.12 12.516-15.694 2.479-21.298 6.535-33.792 7.51-54.56 4.252-64.943-32.562-103.88-47.561-5.108-1.966-11.236-2.437-16.271-3.754-3.554-0.933-13.292 0.196-13.767-7.51 0.09-5.264 13.964 2.216 15.02 0-1.541-2.722-13.286-6.875-7.511-8.762 35.521 12.869 61.882 56.016 106.38 61.327 22.711 2.71 44.086-4.475 67.584-6.259 25.915-1.966 51.778 2.017 75.094-6.258-5.259-2.249-14.193-0.824-20.024-2.503-0.108-7.2-0.271-14.455 1.252-20.025 6.923-2.259 13.888 0.48 21.276 1.252 3.234 5.568 2.444 14.378 3.754 18.773 17.154-49.229-104.77-10.208-123.9-36.295 32.543 9.766 100.99 5.861 148.94 3.754 34.288-1.507 102.81 3.411 81.353-31.289 2.749 4.647 1.047 13.937-1.256 17.522zm-41.3-108.88c-1.144 2.194-0.43 6.245-2.503 7.51 4.209 1.758 10.931 2.934 15.021 1.251 1.364-8.457-6.901-7.285-12.518-8.761zm60.076 55.068v13.768c4.332 1.925 11.41 1.106 17.522 1.252 8.308-11.294-5.278-20.534-17.522-15.02zm-35.044 37.548c-2.397 3.025-0.789 10.057-1.251 15.019h13.768c9.719-7.536-0.683-22.006-12.517-15.019zm-111.39 45.057c-1.666 3.758-1.273 9.576-1.252 15.021 7.063-0.39 11.13 2.219 18.773 1.251 2.625-3.851 1.255-12.357-1.252-15.021-6.133 0.292-9.475-2.205-16.269-1.251z"/>
+ </g>
+ <g id="g2982" transform="matrix(-.33587 -.48446 .48446 -.33587 255.64 813.8)" fill-rule="evenodd">
+ <path id="path3-4" d="m0 129.73c5.056 1.287 3.347 9.338 10.379 8.649 2.95-0.709-0.364-2.23 0-5.189 4.51 2.985 8.768 6.223 8.649 13.838-6.934-2.29-11.824-6.629-19.028-8.649v-8.649z"/>
+ <path id="path5-0" d="m19.028 131.46c3.663 16.518 24.55 15.811 34.596 25.947-11.769-1.308-20.868-9.217-29.407-8.649 0.714-8.21-5.903-9.088-5.189-17.298z"/>
+ <path id="path7-9" d="m247.36 153.95c9.951-0.126 16.131 18.447 20.758 29.405-8.991-7.729-11.192-22.252-20.758-29.405z"/>
+ <path id="path9-4" d="m385.74 205.84c4.146 3.703-5.014 8.726-10.379 8.649 1.757-4.586 7.999-4.686 10.379-8.649z"/>
+ <path id="path11-8" d="m321.74 243.9c4.889 3.89-2.845 9.726-3.46 13.838-4.889-3.89 2.842-9.726 3.46-13.838z"/>
+ <path id="path13-8" d="m0 153.95c10.402 3.682 40.146 8.392 53.624 13.838 40.282 16.275 58.092 66.777 124.55 51.894-6.811-5.875-15.101-10.27-22.488-15.567 13.222 1.081 20.92 12.012 34.596 17.297 7.584 2.931 18.474 2.089 27.677 6.92 11.564 6.071 21.198 19.362 32.866 27.677 24.197 17.243 40.023 31.271 64.002 6.919-3.854 7.102-9.293 12.617-19.027 13.838 18.444 3.007 35.974-11.953 36.325-25.946 0.427-16.97-25.394-39.572-36.325-57.084-19.65-31.474-23.18-54.689-36.326-79.57 5.988-3.042 6.72 13.99 13.838 15.568 7.606-5.078 5.466-19.906 15.568-22.487-1.614 8.764-4.848 15.91-6.918 24.217 5.624 3.309 9.881 3.427 13.838 1.73 6.081-24.971 9.578-51.964 32.865-55.354-13.657 11.844-36.839 46.024-15.568 65.732 14.236-11.135 18.784-31.958 34.597-41.515 3.048 6.002-1.768 7.836 8.648 5.189-5.946 5.585-16.236 6.826-20.757 13.838 2.43 2.873 9.672-4.067 17.297-1.73-15.773 4.983-27.891 13.624-31.135 31.137 1.87 12.262 21.477 13.787 25.946 32.865 1.311 5.597-2.26 12.064-1.729 17.299 2.02 19.947 29.403 38.392 39.785 15.568 2.611 5.559-0.23 6.872 5.189 6.919 8.24-0.327 15.966-9.178 17.298-20.758 5.03-43.67-5.31-129.17-20.76-152.23-5.61-8.383-16.99-15.295-29.4-17.293-58.25-9.38-117.67 20.656-172.98 20.757-68.45 0.125-126.98-38.346-185.09-48.435v-5.189c28.95 8.619 55.573 15.987 83.03 24.217 25.389 7.61 52.703 19.662 83.03 22.488 34.634 3.226 63.573-4.378 93.409-10.379 44.027-8.854 116.44-25.423 136.65 13.838 10.15 19.708 10.938 52.111 13.839 74.381 3.635 27.895 10.597 61.064 5.189 83.03-1.816 7.374-10.973 14.042-17.298 25.946-5.971 11.24-8.695 21.203-15.568 25.947-4.525 3.125-12.553 0.71-20.757 5.189-8.051 4.396-10.497 15.109-22.488 19.028-8.403 2.746-20.753 1.121-31.136 3.459-12.255 2.761-19.113 9.001-27.677 8.649-13.187-0.541-39.07-11.738-53.624-22.488-7.122-5.26-9.862-15.02-17.298-20.757-27.34-21.11-82.78-17.64-112.42-31.14-22.143-10.08-32.794-35.57-50.167-43.24-13.597-6.01-35.367-5.07-46.703-8.65v-15.57zm364.99 102.06c3.907-6.804 8.095-2.571 12.109-8.649 2.249 4.296-5.075 7.179 0 8.649 4.095-6.283 10.517-10.24 12.108-19.028-11.298-0.811-22.458-1.761-32.867-3.46-2.915 8.062-3.552 24.269 8.65 22.488z"/>
+ <path id="path15-2" d="m219.68 140.11c1.396-5.323 10.558 10.831 10.379 1.729 6.086 5.444 12.56 10.504 13.838 20.758-9.095-6.473-15.122-16.014-24.217-22.487z"/>
+ <path id="path17-4" d="m164.33 162.6c1.46-2.458 8.919 3.65 10.379 6.919 2.568-0.755-0.683-2.213 0-5.189 7.596 6.242 14.36 13.316 22.487 19.027-10.021-0.957-25.064-11.824-32.866-20.757z"/>
+ <path id="path19-5" d="m207.58 138.38c-6.535-3.267-16.759-2.845-19.027-10.379 7.358-3.752 17.359 2.874 19.027 10.379z"/>
+ </g>
+ <g id="g3028" transform="matrix(.94236 .33461 -.33461 .94236 -172.81 169.56)" fill-rule="evenodd">
+ <path id="path3-1" d="m361.4 18.798c0.183 3.935-2.72 8.736 0.351 11.859 0.581-1.918 2.404-2.862 1.882-1.278 3.681-0.369-0.205-4.866 1.708-7.206-2.92-0.673-1.599-2.539-3.941-3.375z"/>
+ <path id="path5-7" d="m323.56 19.741c-1.958 1.333-1.575 8.389 0.848 10.972 2.115-4.334-1.078-7.171-0.848-10.972z"/>
+ <path id="path7-1" d="m373.98 89.99c-3.714 1.095-4.415 7.683-4.585 10.26 3.952-1.163 2.957-7.273 4.585-10.26z"/>
+ <path id="path9-1" d="m321.97 72.5c-2.411 1.459-2.76 8.722-4.691 12.531 4.131-1.946 5.219-9.416 4.691-12.531z"/>
+ <path id="path11-5" d="m270.18 127.44c-4.752 9.811-19.64 1.719-24.672-8 5.153-4.7 10.842-7.498 16.442-10.608 2.885 6.549 11.457 11.943 8.23 18.608zm-21.122-6.01c4.796 2.948 9.505 7.987 15.086 8.457 11.746-11.772-8.793-23.322-15.086-8.457z"/>
+ <path id="path13-2" d="m253.65 111.17c-2.67-1.188-6.125 1.818-5.647 3.832 2.115-0.45 3.98-1.786 5.647-3.832z"/>
+ <path id="path15-7" d="m2.984 27.804c19.307-0.023 39.398-2.352 60.289-3.524 19.921-1.12 40.249-0.095 60.179-1.253 17.961-1.042 38.129-5.02 57.842-9.561 20.324-4.681 39.488-12.878 57.559-13.218 13.051-0.246 29.157 4.603 43.064 4.315 9.181-0.19 19.653-2.939 30.252-4.035 21.304-2.203 44.943 3.055 65.787 5.389 12.365 1.385 29.646 4.323 29.889 13.256 0.524 19.289-27.008 14.056-36.328 16.955-13.509 1.351-27.446-2.572-41.397-1.047-7.37 0.806-14.535 4.626-21.66 5.351-9.644 0.983-20.19-3.831-27.589 5.527 42.685 20.521 87.223 26.455 127.48 49.3 5.767 34.513-41.071 12.854-62.277 7.074-26.268-7.161-48.3-16.484-64.968-13.095-0.35 10.063 16.708 15.225 17.493 24.967-10.56 23.538-31.963 8.575-42.513 32.15-10.699-0.138-23.185 6.185-32.632 6.199-7.419 0.01-15.125-4.914-25.168-7.113-23.599-5.165-59.929-1.638-81.196-7.028-22.981-5.826-35.052-17.105-52.047-7.019-1.775-0.994-3.549-1.988-5.325-2.983 8.888-5.595 19.42-5.348 29.083-8.189 0.795-1.107-2.106-4.701 1.102-4.046-0.22 1.557 0.171 2.94 1.171 4.154 0.967-2.802-1.314-8.549 1.813-9.479 0.1 3.21-0.55 6.631 1.843 9.196 21.318-4.805 47.895-5.07 65.761-13.287-18.073 0.189-38.065 2.406-58.541-14.155 28.437 15.536 61.903 14.136 83.4-1.064-5.337-3.071-11.74-3.682-13.7-8.847 1.478 0.828 2.957 1.657 4.436 2.487-1.045-6.191-9.188-14.342-6.843-18.997-0.98 21.186 32.324 21.716 50.648 36.556 3.521 2.852 4.986 8.622 8.552 11.79 12.459 11.079 38.325 27.914 43.629 11.63 1.475-4.527-5.874-17.871-9.398-22.763-6.85-9.518-17.31-22.53-25.97-28.56-15.51-10.8-35.67-11.84-39.22-27.814 5.059 3.557 6.6 8.104 13.593 11.116 3.937-3.393-0.896-8.159 2.702-8.98 0.786 2.865 0.175 8.934 4.612 8.417 3.356-4.005 6.206-9.805 10.124-11.819-2.581 5.461-8.058 11.736-6.682 16.081 14.856 5.141 28.032 15.589 34.783 25.328 31.917-10.33 73.244 4.919 112.58 17.624 10.837 3.501 33.131 11.804 36.853 0.833 2.115-6.233-8.018-11.988-14.763-15.274-34.38-16.744-79.11-26.327-115.55-41.446-1.908-0.621-8.7 3.843-6.816-0.324 10.694-11.2 25.805-10.954 39.771-12.692 5.622-0.7 11.034-2.603 17.008-3.294 26.229-3.041 50.338 4.846 68.879-2.207 6.501-2.474 12.802-8.383 8.456-15.085-33.85-7.884-68.86-11.57-103.92-8.105-18.22 1.801-33.92 0.942-50.98-0.589-32.88-2.953-66.64 12.387-96.51 18.207-16.4 3.195-32.97 1.52-50.21 2.181-33.113 1.266-69.593 3.773-101.4 6.13 0.995-1.775 1.99-3.55 2.984-5.324zm261.6 63.842c0.395 11.847 9.998 21.099 12.129 32.457 6.438 0.748 12.617 0.576 16.336-8.335-5.655-8.494-16.405-22.274-28.465-24.122zm-43.467 23.451c-2.333-0.235 0.908 10.869 4.329 4.76-2.22-1.369-3.948-2.875-4.329-4.76zm34.5 21.673c-11.813-7.808-30.909-17.961-31.703-3.777-8.923-9.287-27.699-17.935-34.863-4.381 7.407-3.651 15.946-0.771 21.513 7.394-0.996 1.774-1.99 3.55-2.985 5.324 4.788 2.848 9.016 3.707 13.418 5.189 8.938-5.838 28.133 3.156 34.62-9.749zm-64.013-4.395c4.305 3.021 6.208 6.721 12.316 9.237 0.995-1.775 1.99-3.551 2.984-5.325-2.4-4.755-11.471-9.038-15.3-3.912z"/>
+ <path id="path17-6" d="m320.29 21.408c-1.362-2.508-1.234 5.898-0.82 7.705 3.498-0.554 0.371-5.536 0.82-7.705z"/>
+ <path id="path19-1" d="m239.77 96.394c-2.115 0.449-3.981 1.787-5.646 3.832-0.551-3.857 6.066-4.896 7.354-11.038-5.153 2.885-9.686 10.019-12.503 13.983 3.877-1.264 7.735-2.605 10.795-6.777z"/>
+ <path id="path21-4" d="m61.931 91.17c1.902 1.954 2.553 4.263 1.951 6.924-16.353-0.565-31.533 3.027-47.544 3.672 15.331 5.78 32.779-3.897 50.486 1.477-0.191-3.683-1.925-6.93-2.231-10.58 1.717-3.636 5.314-0.588 7.032-4.223-20.932-0.899-40.87 1.712-60.396 5.797 14.697 1.758 33.589-4.559 50.702-3.067z"/>
+ </g>
+ <path id="path3234" marker-end="url(#Arrow2Mend)" d="m192.86 538.08-65.72-114.29" stroke="#000" stroke-width="4" fill="none"/>
+ <path id="path3234-2" marker-end="url(#Arrow2Mend)" d="m193.47 352.93 130.62 17.825" stroke="#000" stroke-width="4" fill="none"/>
+ <path id="path3234-8" marker-end="url(#Arrow2Mend)" d="m416.72 467.9-105.26 79.37" stroke="#000" stroke-width="4" fill="none"/>
+ </g>
+ </g>
+ <metadata>
+ <rdf:RDF>
+ <cc:Work>
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/>
+ <dc:publisher>
+ <cc:Agent rdf:about="http://openclipart.org/">
+ <dc:title>Openclipart</dc:title>
+ </cc:Agent>
+ </dc:publisher>
+ <dc:title>Rock Scissors Paper</dc:title>
+ <dc:date>2010-05-31T17:54:35</dc:date>
+ <dc:description>Rock Scissors Paper hand game depiction. Rock, scissors, and paper by Francesco 'Architetto' Rollandin from openclipart.org</dc:description>
+ <dc:source>http://openclipart.org/detail/63805/rock-scissors-paper-by-mazeo</dc:source>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>mazeo</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>clip art</rdf:li>
+ <rdf:li>clipart</rdf:li>
+ <rdf:li>fingers</rdf:li>
+ <rdf:li>fist</rdf:li>
+ <rdf:li>hand</rdf:li>
+ <rdf:li>hands</rdf:li>
+ <rdf:li>paper</rdf:li>
+ <rdf:li>paper rock scissors</rdf:li>
+ <rdf:li>paper scissors rock</rdf:li>
+ <rdf:li>remix</rdf:li>
+ <rdf:li>rock</rdf:li>
+ <rdf:li>rock paper scissors</rdf:li>
+ <rdf:li>rock scissors paper</rdf:li>
+ <rdf:li>scissor</rdf:li>
+ <rdf:li>scissors</rdf:li>
+ <rdf:li>scissors paper rock</rdf:li>
+ <rdf:li>scissors rock paper</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+</svg>
diff --git a/app/assets/javascripts/alerts/edit.js.coffee b/app/assets/javascripts/alerts/edit.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/alerts/edit.js.coffee
diff --git a/app/assets/javascripts/alerts/index.js.coffee b/app/assets/javascripts/alerts/index.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/alerts/index.js.coffee
diff --git a/app/assets/javascripts/alerts/new.js.coffee b/app/assets/javascripts/alerts/new.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/alerts/new.js.coffee
diff --git a/app/assets/javascripts/alerts/show.js.coffee b/app/assets/javascripts/alerts/show.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/alerts/show.js.coffee
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index d6925fa..1130838 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -13,4 +13,8 @@
//= require jquery
//= require jquery_ujs
//= require turbolinks
-//= require_tree .
+//= require 'drag.js'
+//= require 'dragsort.js'
+//= require 'coordinates.js'
+//
+//= require_tree ./application
diff --git a/app/assets/javascripts/application/.keep b/app/assets/javascripts/application/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/application/.keep
diff --git a/app/assets/javascripts/application/layout.js.coffee b/app/assets/javascripts/application/layout.js.coffee
new file mode 100644
index 0000000..da0bc67
--- /dev/null
+++ b/app/assets/javascripts/application/layout.js.coffee
@@ -0,0 +1,21 @@
+json_url = "/alerts.json"
+
+page_visited = false
+starting_size = 0
+update = (alerts) ->
+ if !page_visited
+ starting_size = alerts.length
+ page_visited = true
+
+ if alerts.length > starting_size
+ $("#alerts-ajax").css("display", "inline");
+ return
+
+ setTimeout (->
+ $.ajax(url: json_url).done update
+ return
+ ), 2000
+
+# Now kick off the whole process
+window.onload = ->
+ $.ajax(url: json_url).done update \ No newline at end of file
diff --git a/app/assets/javascripts/brackets/edit.js.coffee b/app/assets/javascripts/brackets/edit.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/brackets/edit.js.coffee
diff --git a/app/assets/javascripts/brackets/index.js.coffee b/app/assets/javascripts/brackets/index.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/brackets/index.js.coffee
diff --git a/app/assets/javascripts/brackets/new.js.coffee b/app/assets/javascripts/brackets/new.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/brackets/new.js.coffee
diff --git a/app/assets/javascripts/brackets/show.js.coffee b/app/assets/javascripts/brackets/show.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/brackets/show.js.coffee
diff --git a/app/assets/javascripts/games/edit.js.coffee b/app/assets/javascripts/games/edit.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/games/edit.js.coffee
diff --git a/app/assets/javascripts/games/index.js.coffee b/app/assets/javascripts/games/index.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/games/index.js.coffee
diff --git a/app/assets/javascripts/games/new.js.coffee b/app/assets/javascripts/games/new.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/games/new.js.coffee
diff --git a/app/assets/javascripts/games/show.js.coffee b/app/assets/javascripts/games/show.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/games/show.js.coffee
diff --git a/app/assets/javascripts/main/homepage.js.coffee b/app/assets/javascripts/main/homepage.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/main/homepage.js.coffee
diff --git a/app/assets/javascripts/matches/show.js.coffee b/app/assets/javascripts/matches/show.js.coffee
new file mode 100644
index 0000000..2609a69
--- /dev/null
+++ b/app/assets/javascripts/matches/show.js.coffee
@@ -0,0 +1,2 @@
+window.onload = ->
+ BetterDragSort.makeListSortable(document.getElementById("boxes"));
diff --git a/app/assets/javascripts/pms/edit.js.coffee b/app/assets/javascripts/pms/edit.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/pms/edit.js.coffee
diff --git a/app/assets/javascripts/pms/index.js.coffee b/app/assets/javascripts/pms/index.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/pms/index.js.coffee
diff --git a/app/assets/javascripts/pms/new.js.coffee b/app/assets/javascripts/pms/new.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/pms/new.js.coffee
diff --git a/app/assets/javascripts/pms/show.js.coffee b/app/assets/javascripts/pms/show.js.coffee
new file mode 100644
index 0000000..559513c
--- /dev/null
+++ b/app/assets/javascripts/pms/show.js.coffee
@@ -0,0 +1,25 @@
+json_url = window.location.href.replace(/\.[^/]*$/,'')+".json"
+
+page_visited_pms = false
+starting_size_pms = 0
+update = (pms) ->
+ if !page_visited_pms
+ starting_size_pms = pms.conversation.count_messages
+ page_visited_pms = true
+
+ if pms.convesation.count_messages > starting_size_pms
+ window.location.reload true
+ return
+
+ console.log("hey we got here!")
+ console.log(starting_size_pms)
+ console.log(pms.convesation.count_messages)
+
+ setTimeout (->
+ $.ajax(url: json_url).done update
+ return
+ ), 2000
+
+# Now kick off the whole process
+window.onload = ->
+ $.ajax(url: json_url).done update \ No newline at end of file
diff --git a/app/assets/javascripts/search/go.js.coffee b/app/assets/javascripts/search/go.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/search/go.js.coffee
diff --git a/app/assets/javascripts/server/edit.js.coffee b/app/assets/javascripts/server/edit.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/server/edit.js.coffee
diff --git a/app/assets/javascripts/server/show.js.coffee b/app/assets/javascripts/server/show.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/server/show.js.coffee
diff --git a/app/assets/javascripts/sessions/new.js.coffee b/app/assets/javascripts/sessions/new.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/sessions/new.js.coffee
diff --git a/app/assets/javascripts/teams/edit.js.coffee b/app/assets/javascripts/teams/edit.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/teams/edit.js.coffee
diff --git a/app/assets/javascripts/teams/index.js.coffee b/app/assets/javascripts/teams/index.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/teams/index.js.coffee
diff --git a/app/assets/javascripts/teams/new.js.coffee b/app/assets/javascripts/teams/new.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/teams/new.js.coffee
diff --git a/app/assets/javascripts/teams/show.js.coffee b/app/assets/javascripts/teams/show.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/teams/show.js.coffee
diff --git a/app/assets/javascripts/tournaments/edit.js.coffee b/app/assets/javascripts/tournaments/edit.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/tournaments/edit.js.coffee
diff --git a/app/assets/javascripts/tournaments/index.js.coffee b/app/assets/javascripts/tournaments/index.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/tournaments/index.js.coffee
diff --git a/app/assets/javascripts/tournaments/new.js.coffee b/app/assets/javascripts/tournaments/new.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/tournaments/new.js.coffee
diff --git a/app/assets/javascripts/tournaments/show.js.coffee b/app/assets/javascripts/tournaments/show.js.coffee
new file mode 100644
index 0000000..cd1fd08
--- /dev/null
+++ b/app/assets/javascripts/tournaments/show.js.coffee
@@ -0,0 +1,32 @@
+json_url = window.location.href.replace(/\.[^/]*$/,'')+".json"
+
+update = (tournament) ->
+ here = tournament["players"].length
+ needed = (tournament["min_teams_per_match"] * tournament["min_players_per_team"])
+ pct_complete = here / needed
+
+ $("#prog-bar").width (pct_complete * 100) + "%"
+ $("#players-needed").text here + " " + ((if here is 1 then "player has" else "players have")) + " signed up. " + needed + " players needed. "
+
+ # Update the user list
+ players = ""
+ for player in tournament["players"]
+ players = players + "<li>"+player["user_name"]+"</li>"
+ $("#tournament-users").html players
+
+ # Enable/disable the "start" button depending on the number of players
+ $("input[value=\"Start Tournament\"]").prop "disabled", (if (pct_complete >= 1) then false else true)
+
+ # Switch views if the tournament has been started
+ if tournament["status"] is 1
+ window.location.reload true
+
+ # Do it all again
+ setTimeout (->
+ $.ajax(url: json_url).done update
+ return
+ ), 2000
+
+# Now kick off the whole process
+window.onload = ->
+ $.ajax(url: json_url).done update
diff --git a/app/assets/javascripts/users/edit.js.coffee b/app/assets/javascripts/users/edit.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/users/edit.js.coffee
diff --git a/app/assets/javascripts/users/index.js.coffee b/app/assets/javascripts/users/index.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/users/index.js.coffee
diff --git a/app/assets/javascripts/users/new.js.coffee b/app/assets/javascripts/users/new.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/users/new.js.coffee
diff --git a/app/assets/javascripts/users/show.js.coffee b/app/assets/javascripts/users/show.js.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/javascripts/users/show.js.coffee
diff --git a/app/assets/stylesheets/alerts.css.scss b/app/assets/stylesheets/alerts.css.scss
index c01a620..e69de29 100644
--- a/app/assets/stylesheets/alerts.css.scss
+++ b/app/assets/stylesheets/alerts.css.scss
@@ -1,3 +0,0 @@
-// Place all the styles related to the alerts controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
index 3192ec8..1c8b233 100644
--- a/app/assets/stylesheets/application.css
+++ b/app/assets/stylesheets/application.css
@@ -5,9 +5,8 @@
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
*
- * You're free to add application-wide styles to this file and they'll appear at the top of the
- * compiled file, but it's generally better to create a new file per style scope.
- *
*= require_self
- *= require_tree .
+ *= require_tree ./application
*/
+
+/* If you put any styles in this file directly, I will knife you. */
diff --git a/app/assets/stylesheets/application/scaffolds.css.scss b/app/assets/stylesheets/application/scaffolds.css.scss
new file mode 100644
index 0000000..4f0c781
--- /dev/null
+++ b/app/assets/stylesheets/application/scaffolds.css.scss
@@ -0,0 +1,329 @@
+@import "bootstrap";
+@import "colors";
+
+/* Layout ***********************************************************/
+
+html {
+ body {
+ background: asset-url("bg.png", image) repeat scroll 0 0 $toolbar-color;
+ color: $page-color;
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+
+ header > nav {
+ @extend .navbar;
+ @extend .navbar-inverse;
+ color: white;
+
+ .navbar-brand {
+ @extend .no-dec;
+ a{
+ color: white;
+ &:hover, &:active, &:focus {
+ color: white;
+ font-weight: normal;
+ text-decoration: none;
+ }
+ }
+ }
+
+ #log-buttons {
+ margin-top: 8px;
+ form { display: inline; }
+ #alerts-ajax {
+ display: none;
+ }
+ }
+
+ form.search {
+ @extend .navbar-form;
+ float: right;
+ input[type="text"], input[type="search"] {
+ background-color: #303030;
+ border: 2px solid #ED9C28;
+ border-radius: 5px;
+ color: #FFF;
+ font-weight: bold;
+ height: 30px;
+ padding: 0px 5px;
+ }
+ input[type="submit"] {
+ @extend .btn-warning;
+ }
+ }
+ }
+
+ #notice {
+ background-color: rgba(0,0,0,0.5);
+ border-radius: 7px;
+ padding: 10px;
+ width: 80%;
+ margin: 0px auto;
+ text-align: center;
+
+ p {
+ color: lightgreen !important;
+ font-weight: bold;
+ }
+ }
+
+ .wrapper {
+ width: 80%;
+ margin: 10px auto 0;
+ }
+
+ footer {
+ clear: both;
+ border-top: solid 1px $orange;
+ text-align: center;
+ margin: 1em auto 0;
+ width: 90%;
+ }
+ }
+}
+
+/* General styles ***************************************************/
+
+h1, h2, h3, h4, h5, p, li, label{
+ color: $orange;
+}
+
+h1, h2, h3, h4, h5 {
+ text-shadow: 0px 0px 2px $darker-orange;
+}
+
+p, ol, ul, td {
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+}
+
+fieldset {
+ border: solid 1px $orange;
+ border-radius: .5em;
+ padding: 1em;
+ margin: 1em 0;
+ legend {
+ color: $orange;
+ display: block;
+ margin: 0;
+ padding: .25em .5em;
+ width: auto;
+ border: solid 1px $orange;
+ border-radius: .5em;
+ }
+}
+
+form ul {
+ list-style: none;
+}
+
+a, button, input[type="submit"] {
+ @extend .btn;
+ &.user { @extend .btn-info; }
+ &.signup { @extend .btn-success; }
+ &.signin { @extend .btn-warning; }
+ &.signout { @extend .btn-danger; }
+ &.server { @extend .btn-warning; }
+ &.create-alert {
+ color: white;
+ background-color: rgb(255, 69, 0);
+ border-color: rgb(255, 69, 0);
+ }
+ &.alerts {
+ @extend .navbar-right;
+ @extend .glyphicon;
+ @extend .glyphicon-exclamation-sign;
+ color: white;
+ background-color: hsl(0, 69%, 22%) !important;
+ }
+ font-weight: bold !important;
+}
+
+input, textarea{
+ -webkit-box-shadow: inset 0 0 8px rgba(0,0,0,0.1), 0 0 16px rgba(0,0,0,0.1);
+ -moz-box-shadow: inset 0 0 8px rgba(0,0,0,0.1), 0 0 16px rgba(0,0,0,0.1);
+ box-shadow: inset 0 0 8px rgba(0,0,0,0.1), 0 0 16px rgba(0,0,0,0.1);
+
+ border: 3px inset #A5A5A5;
+ padding: 8px;
+ color: $orange;
+ background: rgba(0,0,0,0.5);
+ margin: 0 0 5px 0;
+}
+
+select {
+ background-color: #333;
+ padding: 5px;
+ border: none;
+ color: $orange;
+}
+
+pre {
+ text-align: left;
+ background-color: #eee;
+ padding: 10px;
+ font-size: 11px;
+}
+
+a {
+ color: $link-yellow;
+ &:hover {
+ color: white;
+
+ }
+}
+
+p, li {
+ color: #DD9125;
+}
+
+td, th {
+ color: #DD9125;
+}
+
+svg {
+ min-width: 600px;
+}
+
+div.field, div.actions {
+ margin-bottom: 10px;
+}
+
+#error_explanation {
+ width: 450px;
+ border: 2px solid red;
+ padding: 7px;
+ padding-bottom: 0;
+ margin-bottom: 20px;
+ background-color: #f0f0f0;
+ h2 {
+ text-align: left;
+ font-weight: bold;
+ padding: 5px 5px 5px 15px;
+ font-size: 12px;
+ margin: -7px;
+ margin-bottom: 0px;
+ background-color: #c00;
+ color: #fff;
+ }
+ ul li {
+ font-size: 12px;
+ list-style: square;
+ }
+}
+.field_with_errors {
+ padding: 1px;
+ background-color: #FF4C4C;
+ box-shadow: 0px 0px 5px red;
+ display: table;
+}
+.errors {
+ background-color: rgba(0,0,0,0.5);;
+ color: red;
+ border-radius: 7px;
+ padding: 10px;
+}
+
+/* Specific styles **************************************************/
+
+.tournament-listing {
+ margin: 10px 0px;
+ border-radius: 5px;
+ box-shadow: 0px 0px 3px #B8B8B8;
+ background-color: rgba(0, 0, 0, 0.6);
+ border: 1px solid #AAAAAA;
+ min-height: 100px;
+ padding: 8px 4px;
+
+ div.row {
+ margin-left: 2%;
+ }
+ p.message {
+ margin-top: 10px;
+ }
+
+ /* AKA the listing title */
+ h3 {
+ margin-top: 0px;
+ color: #F0AD4E;
+ font-weight: bold;
+ }
+
+ h3:hover {
+ color: #D09D3E;
+ }
+
+ /* host of the tournament */
+ .host {
+ font-weight: bold;
+ color: #FFF;
+ }
+
+ .col-md-8 {
+ padding: 0;
+ a {
+ padding: 5px 0 0 0;
+ }
+ }
+
+ .t-game{
+ font-weight: bold;
+ text-align: center;
+ }
+
+ .t-image{
+ display: block;
+ margin:auto;
+ }
+}
+
+
+.user-listing {
+ margin: 10px 0px;
+ border-radius: 5px;
+ box-shadow: 0px 0px 3px #B8B8B8;
+ background-color: rgba(0, 0, 0, 0.6);
+ border: 1px solid #AAAAAA;
+ min-height: 100px;
+ padding: 8px 4px;
+ display: inline-table;
+
+ /* AKA the listing title */
+ h3 {
+ margin-top: 0px;
+ color: #F0AD4E;
+ font-weight: bold;
+ }
+
+ h3:hover {
+ color: #D09D3E;
+ }
+
+ .things {
+ padding: 0px 10px;
+ }
+
+ p {
+ margin: 0;
+ }
+ div.row {
+ margin-left: 2%;
+ }
+}
+
+// Limitation: Only one box can be expanded at a time
+.collapsible {
+ .collapsed { display: block; }
+ .expanded { display: none; }
+ &:target {
+ .collapsed { display: none; }
+ .expanded { display: block; }
+ }
+}
+
+.simple_captcha {
+ background-color: rgba(255, 255, 255, 0.7);
+ margin: 10px 0px;
+}
diff --git a/app/assets/stylesheets/brackets.css.scss b/app/assets/stylesheets/brackets.css.scss
index 481e6e6..e69de29 100644
--- a/app/assets/stylesheets/brackets.css.scss
+++ b/app/assets/stylesheets/brackets.css.scss
@@ -1,3 +0,0 @@
-// Place all the styles related to the brackets controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/colors.css.scss b/app/assets/stylesheets/colors.css.scss
new file mode 100644
index 0000000..c875caa
--- /dev/null
+++ b/app/assets/stylesheets/colors.css.scss
@@ -0,0 +1,5 @@
+$darker-orange: #9D4102;
+$link-yellow: #FFC50D;
+$orange: #DD9125;
+$page-color: #444;
+$toolbar-color: black;
diff --git a/app/assets/stylesheets/games.css.scss b/app/assets/stylesheets/games.css.scss
index db1b7bc..e69de29 100644
--- a/app/assets/stylesheets/games.css.scss
+++ b/app/assets/stylesheets/games.css.scss
@@ -1,3 +0,0 @@
-// Place all the styles related to the games controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/main.css.scss b/app/assets/stylesheets/main.css.scss
index a0d94c1..15f5ac5 100644
--- a/app/assets/stylesheets/main.css.scss
+++ b/app/assets/stylesheets/main.css.scss
@@ -1,3 +1,13 @@
// Place all the styles related to the main controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
+
+.jumbotron {
+ background-color: #FFF;
+ box-shadow: 1px 1px 20px black;
+ width: 93%;
+
+ p {
+ line-height: 1.5em;
+ }
+}
diff --git a/app/assets/stylesheets/matches.css.scss b/app/assets/stylesheets/matches.css.scss
index 4c396e3..e3ad6e7 100644
--- a/app/assets/stylesheets/matches.css.scss
+++ b/app/assets/stylesheets/matches.css.scss
@@ -1,3 +1,36 @@
-// Place all the styles related to the matches controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
+#peer_review_boxes li {
+ cursor: move;
+ position: relative;
+ float: left;
+ margin: 5px;
+ width: 180px;
+ height: 240px;
+ border: 1px solid rgb(0, 0, 0);
+ text-align: center;
+ padding-top: 10px;
+ background-color: rgb(238, 238, 255);
+}
+
+// Nothing uses this one right now
+#peer_review_numeric li {
+ cursor: move;
+ position: relative;
+ float: left;
+ margin: 5px;
+ width: 180px;
+ height: 240px;
+ border: 1px solid rgb(0, 0, 0);
+ text-align: center;
+ padding-top: 10px;
+ background-color: rgb(238, 238, 255);
+}
+
+/**** INDEX PAGE - TABLE AND GRAPH ****/
+#matches-table {
+ @extend .table;
+ color: #FFF;
+
+ form {
+ color: #333;
+ }
+}
diff --git a/app/assets/stylesheets/patch b/app/assets/stylesheets/patch
new file mode 100644
index 0000000..0997417
--- /dev/null
+++ b/app/assets/stylesheets/patch
@@ -0,0 +1,54 @@
+commit 10f01633176ca214e7aec6be61ed3344035ec77e
+Merge: 99dff7e 20f7b74
+Author: webb39 <webb39@purdue.edu>
+Date: Mon Mar 10 20:41:27 2014 -0400
+
+ Merge branch 'master' of https://github.com/LukeShu/Leaguer
+
+commit 99dff7e01a65986338824804651367e97a0d1923
+Merge: 1f00553 f0c03cd
+Author: webb39 <webb39@purdue.edu>
+Date: Mon Mar 10 20:41:16 2014 -0400
+
+ Merge https://github.com/LukeShu/Leaguer
+
+ Conflicts:
+ doc/Sprint1-Retrospective.md
+
+commit 1f00553cbc5d281efe3ac1b434d16537a17bc969
+Author: webb39 <webb39@purdue.edu>
+Date: Mon Mar 10 20:36:31 2014 -0400
+
+ added match controller information
+
+diff --git a/doc/Sprint1-Retrospective.md b/doc/Sprint1-Retrospective.md
+index 3da3669..ae1b07a 100644
+--- a/doc/Sprint1-Retrospective.md
++++ b/doc/Sprint1-Retrospective.md
+@@ -97,13 +97,24 @@ f
+ f
+
+ ## Login (UI) {#login-ui}
+-
++
+ ## Tournament settings {#tourney-settings}
+-
++
+ ## Tournament registration {#tourney-registration}
+
+ ## Match controller {#match-controller}
+
++The Match Controller creates the separate matches for a specific tournament.
++When a tournament is started, it begins with an initial match that contains
++no players. Currently, a player must join a match by entering the specific
++tournament (by clicking the 'show' button on the tournament),
++then they must enter the match (again by clicking the 'show' button but this
++time on the match they desire to participate in) and then finally clicking
++the 'join' button. This updates the match with the user as a participant in
++the matc and then finally clicking the 'join' button. This updates the match
++with the user as a participant in the match. A match can also be destroyed
++by clicking the 'delete' button on the no longer desired match on the page.
++
+ ## Permissions system {#permissions}
+
+ ## Tournament view {#tourney-view}
diff --git a/app/assets/stylesheets/pms.css.scss b/app/assets/stylesheets/pms.css.scss
index 5106093..e69de29 100644
--- a/app/assets/stylesheets/pms.css.scss
+++ b/app/assets/stylesheets/pms.css.scss
@@ -1,3 +0,0 @@
-// Place all the styles related to the pms controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/scaffolds.css.scss b/app/assets/stylesheets/scaffolds.css.scss
deleted file mode 100644
index 6ec6a8f..0000000
--- a/app/assets/stylesheets/scaffolds.css.scss
+++ /dev/null
@@ -1,69 +0,0 @@
-body {
- background-color: #fff;
- color: #333;
- font-family: verdana, arial, helvetica, sans-serif;
- font-size: 13px;
- line-height: 18px;
-}
-
-p, ol, ul, td {
- font-family: verdana, arial, helvetica, sans-serif;
- font-size: 13px;
- line-height: 18px;
-}
-
-pre {
- background-color: #eee;
- padding: 10px;
- font-size: 11px;
-}
-
-a {
- color: #000;
- &:visited {
- color: #666;
- }
- &:hover {
- color: #fff;
- background-color: #000;
- }
-}
-
-div {
- &.field, &.actions {
- margin-bottom: 10px;
- }
-}
-
-#notice {
- color: green;
-}
-
-.field_with_errors {
- padding: 2px;
- background-color: red;
- display: table;
-}
-
-#error_explanation {
- width: 450px;
- border: 2px solid red;
- padding: 7px;
- padding-bottom: 0;
- margin-bottom: 20px;
- background-color: #f0f0f0;
- h2 {
- text-align: left;
- font-weight: bold;
- padding: 5px 5px 5px 15px;
- font-size: 12px;
- margin: -7px;
- margin-bottom: 0px;
- background-color: #c00;
- color: #fff;
- }
- ul li {
- font-size: 12px;
- list-style: square;
- }
-}
diff --git a/app/assets/stylesheets/search.css.scss b/app/assets/stylesheets/search.css.scss
index 22fd394..e69de29 100644
--- a/app/assets/stylesheets/search.css.scss
+++ b/app/assets/stylesheets/search.css.scss
@@ -1,3 +0,0 @@
-// Place all the styles related to the search controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/servers.css.scss b/app/assets/stylesheets/servers.css.scss
index 4710386..e69de29 100644
--- a/app/assets/stylesheets/servers.css.scss
+++ b/app/assets/stylesheets/servers.css.scss
@@ -1,3 +0,0 @@
-// Place all the styles related to the servers controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/sessions.css.scss b/app/assets/stylesheets/sessions.css.scss
index 7bef9cf..e69de29 100644
--- a/app/assets/stylesheets/sessions.css.scss
+++ b/app/assets/stylesheets/sessions.css.scss
@@ -1,3 +0,0 @@
-// Place all the styles related to the sessions controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/teams.css.scss b/app/assets/stylesheets/teams.css.scss
index 320d00d..e69de29 100644
--- a/app/assets/stylesheets/teams.css.scss
+++ b/app/assets/stylesheets/teams.css.scss
@@ -1,3 +0,0 @@
-// Place all the styles related to the teams controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/tournaments.css.scss b/app/assets/stylesheets/tournaments.css.scss
index e372b90..0b8aa99 100644
--- a/app/assets/stylesheets/tournaments.css.scss
+++ b/app/assets/stylesheets/tournaments.css.scss
@@ -1,3 +1,47 @@
// Place all the styles related to the tournaments controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
+
+@import "colors";
+
+#players-needed {
+ text-align: center;
+ font-style: italic;
+}
+
+#tournament-side-params {
+ background: none repeat scroll 0 0 rgba(0,0,0,0.5);
+ border-radius: 5px;
+ float: right;
+ font-size: 7px;
+ padding: 10px;
+
+ p {
+ font-size: 10px;
+ margin-bottom: 5px;
+ }
+
+}
+
+ul#tournament-users {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ li {
+ // Make the bullet green, but the text white
+ padding-left: 1em;
+ text-indent: -.7em;
+ &:before {
+ content: "• ";
+ color: #10A010;
+ }
+ color: white;
+ }
+}
+
+div.leave-buttons {
+ margin-top: 50px;
+ form {
+ display: inline;
+ }
+}
diff --git a/app/assets/stylesheets/users.css.scss b/app/assets/stylesheets/users.css.scss
index 1efc835..e69de29 100644
--- a/app/assets/stylesheets/users.css.scss
+++ b/app/assets/stylesheets/users.css.scss
@@ -1,3 +0,0 @@
-// Place all the styles related to the users controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/alerts_controller.rb b/app/controllers/alerts_controller.rb
index a3cb8f9..b728c7e 100644
--- a/app/controllers/alerts_controller.rb
+++ b/app/controllers/alerts_controller.rb
@@ -1,6 +1,4 @@
class AlertsController < ApplicationController
- before_action :set_alert, only: [:show, :edit, :update, :destroy]
-
# GET /alerts
# GET /alerts.json
def index
@@ -25,6 +23,13 @@ class AlertsController < ApplicationController
# POST /alerts.json
def create
@alert = Alert.new(alert_params)
+ @alert.author = current_user
+ users = {}
+ users = User.all
+
+ for i in 0..users.length
+ current_user.send_message(users[i], @alert.message, "Pay Attention!")
+ end
respond_to do |format|
if @alert.save
@@ -62,11 +67,16 @@ class AlertsController < ApplicationController
end
private
+
# Use callbacks to share common setup or constraints between actions.
def set_alert
@alert = Alert.find(params[:id])
end
+ def is_owner?(object)
+ object.author == current_user
+ end
+
# Never trust parameters from the scary internet, only allow the white list through.
def alert_params
params.require(:alert).permit(:author_id, :message)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 27ef6a7..d5752aa 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,5 +1,55 @@
class ApplicationController < ActionController::Base
+ before_action :set_object, only: [:show]
+ before_action :check_create, only: [:new, :create]
+ before_action :check_edit, only: [:edit, :update]
+ before_action :check_delete, only: [:destroy]
+
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
+
+ #include sessionhelper for the session controller and view
+ include SessionsHelper
+
+ include SimpleCaptcha::ControllerHelpers
+
+ def check_permission(verb, object=nil)
+ unless current_user.can?((verb.to_s+"_"+noun).to_sym) or (!object.nil? and is_owner?(object))
+ respond_to do |format|
+ format.html do
+ if object.nil?
+ redirect_to send(noun.pluralize+"_url"), notice: "You don't have permission to #{verb} #{noun.pluralize}."
+ else
+ redirect_to object, notice: "You don't have permission to #{verb} this #{noun}."
+ end
+ end
+ format.json { render json: "Permission denied", status: :forbidden }
+ end
+ end
+ end
+
+ def noun
+ @noun ||= self.class.name.underscore.sub(/_controller$/, '').singularize
+ end
+
+ def set_object
+ object = send("set_"+noun)
+ end
+
+ def check_create
+ check_permission(:create)
+ end
+ def check_edit
+ object = send("set_"+noun)
+ check_permission(:edit, object)
+ end
+ def check_delete
+ object = send("set_"+noun)
+ check_permission(:edit, object)
+ end
+
+ # Override this
+ def is_owner?(object)
+ return false
+ end
end
diff --git a/app/controllers/brackets_controller.rb b/app/controllers/brackets_controller.rb
index fe43ca9..e202c96 100644
--- a/app/controllers/brackets_controller.rb
+++ b/app/controllers/brackets_controller.rb
@@ -1,20 +1,30 @@
class BracketsController < ApplicationController
- before_action :set_bracket, only: [:show, :edit, :update, :destroy]
+ before_action :set_tournament, only: [:index, :create]
# GET /brackets
# GET /brackets.json
def index
- @brackets = Bracket.all
+ @tournament = Tournament.find(params[:tournament_id])
+ @brackets = @tournament.brackets
end
# GET /brackets/1
# GET /brackets/1.json
def show
- end
+ @results = (@tournament.status == 4)? @bracket.calcResult : nil;
+ @matches = @tournament.stages.order(:id).first.matches_ordered
+ @numTeams = @tournament.min_teams_per_match
+ @logBase = @numTeams
+
+ # depth of SVG tree
+ @depth = Math.log(@matches.count*(@logBase-1),@logBase).floor+1;
+
+ # height of SVG
+ @matchHeight = 50*@logBase;
+ @height = [(@matchHeight+50) * @logBase**(@depth-1) + 100, 500].max;
- # GET /brackets/new
- def new
- @bracket = Bracket.new
+ @base = 1
+ @pBase = 1
end
# GET /brackets/1/edit
@@ -24,14 +34,17 @@ class BracketsController < ApplicationController
# POST /brackets
# POST /brackets.json
def create
- @bracket = Bracket.new(bracket_params)
+ @bracket = @tournament.brackets.build(user: current_user)
+ @bracket.name = current_user.user_name + "'s Prediction for " + @tournament.name
respond_to do |format|
- if @bracket.save
+ if @tournament.status == 1 && @tournament.stages.first.scheduling_method == "elimination" && @tournament.stages.first.matches.first.status < 2
+ @bracket.save
+ @bracket.create_matches
format.html { redirect_to @bracket, notice: 'Bracket was successfully created.' }
- format.json { render action: 'show', status: :created, location: @bracket }
+ format.json { render action: 'edit', status: :created, location: @bracket }
else
- format.html { render action: 'new' }
+ format.html { redirect_to tournaments_path action: 'You can\'t make a bracket for this tournament' }
format.json { render json: @bracket.errors, status: :unprocessable_entity }
end
end
@@ -41,11 +54,11 @@ class BracketsController < ApplicationController
# PATCH/PUT /brackets/1.json
def update
respond_to do |format|
- if @bracket.update(bracket_params)
- format.html { redirect_to @bracket, notice: 'Bracket was successfully updated.' }
+ if @bracket.predict_winners(prediction_params)
+ format.html { redirect_to @tournament, notice: 'Your bracket was made! Check back when this stage finishes to see how you did!' }
format.json { head :no_content }
else
- format.html { render action: 'edit' }
+ format.html { redirect_to @tournament, notice: 'bracket was not made... :('}
format.json { render json: @bracket.errors, status: :unprocessable_entity }
end
end
@@ -64,11 +77,32 @@ class BracketsController < ApplicationController
private
# Use callbacks to share common setup or constraints between actions.
def set_bracket
+ @tournament = Tournament.find(params[:tournament_id])
@bracket = Bracket.find(params[:id])
end
+ def set_tournament
+ @tournament = Tournament.find(params[:tournament_id])
+ end
+
# Never trust parameters from the scary internet, only allow the white list through.
def bracket_params
+ # bracket[user_id]
+ # bracket[tournament_id]
+ # bracket[name]
+ # bracket[matches][#{i}]
params.require(:bracket).permit(:user_id, :tournament_id, :name)
end
+
+ def prediction_params
+ require 'pp'
+ puts "<params"+"<"*80
+ pp params
+ puts ">"*80
+ params.require(:bracket).require(:matches)
+ end
+
+ def is_owner?(bracket)
+ bracket.user == current_user
+ end
end
diff --git a/app/controllers/games_controller.rb b/app/controllers/games_controller.rb
index 27df771..d014a1c 100644
--- a/app/controllers/games_controller.rb
+++ b/app/controllers/games_controller.rb
@@ -1,6 +1,4 @@
class GamesController < ApplicationController
- before_action :set_game, only: [:show, :edit, :update, :destroy]
-
# GET /games
# GET /games.json
def index
diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb
index 6519d7b..0ba4d94 100644
--- a/app/controllers/main_controller.rb
+++ b/app/controllers/main_controller.rb
@@ -1,2 +1,4 @@
class MainController < ApplicationController
+ def homepage
+ end
end
diff --git a/app/controllers/matches_controller.rb b/app/controllers/matches_controller.rb
index 4042d3c..e944983 100644
--- a/app/controllers/matches_controller.rb
+++ b/app/controllers/matches_controller.rb
@@ -1,63 +1,77 @@
class MatchesController < ApplicationController
- before_action :set_match, only: [:show, :edit, :update, :destroy]
+ require 'httparty'
+ require 'json'
+ require 'delayed_job'
- # GET /matches
- # GET /matches.json
+ before_action :set_tournament, only: [:index]
+
+ # GET /tournaments/1/matches
+ # GET /tournaments/1/matches.json
def index
- @matches = Match.all
end
- # GET /matches/1
- # GET /matches/1.json
+ # GET /tournaments/1/matches/1
+ # GET /tournaments/1/matches/1.json
def show
- end
-
- # GET /matches/new
- def new
- @match = Match.new
- end
-
- # GET /matches/1/edit
- def edit
- end
-
- # POST /matches
- # POST /matches.json
- def create
- @match = Match.new(match_params)
-
- respond_to do |format|
- if @match.save
- format.html { redirect_to @match, notice: 'Match was successfully created.' }
- format.json { render action: 'show', status: :created, location: @match }
- else
- format.html { render action: 'new' }
- format.json { render json: @match.errors, status: :unprocessable_entity }
- end
+ if @match.tournament_stage.tournament.game_id == 1
+ file_blue = "blue.yaml"
+ file_purple = "purple.yaml"
+ @blue2 = YAML.load_file(file_blue)
+ @purp2 = YAML.load_file(file_purple)
end
end
- # PATCH/PUT /matches/1
- # PATCH/PUT /matches/1.json
+ # PATCH/PUT /tournaments/1/matches/1
+ # PATCH/PUT /tournaments/1/matches/1.json
def update
- respond_to do |format|
- if @match.update(match_params)
- format.html { redirect_to @match, notice: 'Match was successfully updated.' }
+ case @match.status
+ when 0
+ # Created, waiting to be scheduled
+ when 1
+ # Scheduled, waiting to start
+ if (@tournament.hosts.include? current_user) and (params[:update_action] == "start")
+ @match.status = 2
+ @match.start_sampling
+ respond_to do |format|
+ if @match.save
+ format.html { redirect_to tournament_match_path(@tournament, @match), notice: 'Match has started.' }
+ format.json { head :no_content }
+ else
+ format.html { render action: 'show' }
+ format.json { render json: @match.errors, status: :unprocessable_entity }
+ end
+ end
+ return
+ end
+ when 2
+ # Started, waiting to finish
+ @match.handle_sampling(@current_user, params)
+ # The @match.status will be updated by Statistic's after_save hook
+ respond_to do |format|
+ format.html { redirect_to tournament_match_path(@tournament, @match), notice: 'Match has finished.' }
format.json { head :no_content }
- else
- format.html { render action: 'edit' }
- format.json { render json: @match.errors, status: :unprocessable_entity }
end
- end
- end
-
- # DELETE /matches/1
- # DELETE /matches/1.json
- def destroy
- @match.destroy
- respond_to do |format|
- format.html { redirect_to matches_url }
- format.json { head :no_content }
+ when 3
+ if (@tournament.hosts.include? current_user) and (params[:update_action] == "start")
+ ok = true
+ ActiveRecord::Base.transaction do
+ ok &= @match.statitistics.destroy_all
+ @match.status = 1
+ ok &= @match.save
+ end
+ respond_to do |format|
+ if @match.save
+ format.html { redirect_to tournament_match_path(@tournament, @match), notice: 'Match has finished.' }
+ format.json { head :no_content }
+ else
+ format.html { render action: 'show' }
+ format.json { render json: @match.errors, status: :unprocessable_entity }
+ end
+ end
+ return
+ end
+ else
+ redirect_to tournament_match_path(@tournament, @match)
end
end
@@ -65,10 +79,21 @@ class MatchesController < ApplicationController
# Use callbacks to share common setup or constraints between actions.
def set_match
@match = Match.find(params[:id])
+ @tournament = @match.tournament_stage.tournament
+ end
+
+ def set_tournament
+ @tournament = Tournament.find(params[:tournament_id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def match_params
params.require(:match).permit(:status, :tournament_stage_id, :winner_id)
+ params.require(:match).permit(:status, :tournament_stage_id, :winner_id)
+ end
+
+ # Turn of check_edit, since our #update is flexible
+ def check_edit
+ set_match
end
end
diff --git a/app/controllers/pms_controller.rb b/app/controllers/pms_controller.rb
index 11f51c8..3368663 100644
--- a/app/controllers/pms_controller.rb
+++ b/app/controllers/pms_controller.rb
@@ -1,6 +1,4 @@
class PmsController < ApplicationController
- before_action :set_pm, only: [:show, :edit, :update, :destroy]
-
# GET /pms
# GET /pms.json
def index
@@ -25,6 +23,12 @@ class PmsController < ApplicationController
# POST /pms.json
def create
@pm = Pm.new(pm_params)
+ @pm.author = current_user
+ #require 'pp'
+ #pp @pm.message
+ @pm.recipient = User.find_by_user_name(pm_params['recipient_id'])
+
+ @pm.conversation = @pm.author.send_message(@pm.recipient, @pm.message, @pm.subject).conversation
respond_to do |format|
if @pm.save
@@ -37,6 +41,10 @@ class PmsController < ApplicationController
end
end
+ #def reply
+ # current_user.reply_to_conversation(conversation, message)
+ #end
+
# PATCH/PUT /pms/1
# PATCH/PUT /pms/1.json
def update
@@ -49,6 +57,7 @@ class PmsController < ApplicationController
format.json { render json: @pm.errors, status: :unprocessable_entity }
end
end
+ current_user.reply_to_conversation(@pm.conversation, @pm.message)
end
# DELETE /pms/1
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index ee61487..af35ddb 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -1,2 +1,41 @@
class SearchController < ApplicationController
+
+ def go
+ @games = Game.all
+ @query = params[:query]
+ @gametype = params[:game_type]
+
+ if ( @gametype.nil? and (@query.nil? or @query.empty?)) then
+ return
+ end
+
+ tour_filters = []
+ user_filters = []
+ unless @query.empty?
+ tour_filters.push(["name LIKE ?", "%#{@query}%"])
+ user_filters.push(["name LIKE ?", "%#{@query}%"])
+ end
+ unless @gametype.nil? or @gametype.empty?
+ tour_filters.push(["game_id = ?", @gametype])
+ end
+
+ if tour_filters.empty?
+ @tournamets = []
+ else
+ @tournaments = Tournament
+ tour_filters.each do |filter|
+ @tournaments = @tournaments.where(*filter)
+ end
+ end
+
+ if user_filters.empty?
+ @players = []
+ else
+ @players = User
+ user_filters.each do |filter|
+ @players = @players.where(*filter)
+ end
+ end
+ end
+
end
diff --git a/app/controllers/servers_controller.rb b/app/controllers/servers_controller.rb
index 4c12c7e..83a9f31 100644
--- a/app/controllers/servers_controller.rb
+++ b/app/controllers/servers_controller.rb
@@ -1,44 +1,15 @@
class ServersController < ApplicationController
- before_action :set_server, only: [:show, :edit, :update, :destroy]
-
- # GET /servers
- # GET /servers.json
- def index
- @servers = Server.all
- end
-
- # GET /servers/1
- # GET /servers/1.json
+ # GET /server
+ # GET /server.json
def show
end
- # GET /servers/new
- def new
- @server = Server.new
- end
-
- # GET /servers/1/edit
+ # GET /server/edit
def edit
end
- # POST /servers
- # POST /servers.json
- def create
- @server = Server.new(server_params)
-
- respond_to do |format|
- if @server.save
- format.html { redirect_to @server, notice: 'Server was successfully created.' }
- format.json { render action: 'show', status: :created, location: @server }
- else
- format.html { render action: 'new' }
- format.json { render json: @server.errors, status: :unprocessable_entity }
- end
- end
- end
-
- # PATCH/PUT /servers/1
- # PATCH/PUT /servers/1.json
+ # PATCH/PUT /server
+ # PATCH/PUT /server.json
def update
respond_to do |format|
if @server.update(server_params)
@@ -51,24 +22,15 @@ class ServersController < ApplicationController
end
end
- # DELETE /servers/1
- # DELETE /servers/1.json
- def destroy
- @server.destroy
- respond_to do |format|
- format.html { redirect_to servers_url }
- format.json { head :no_content }
- end
- end
-
private
+
# Use callbacks to share common setup or constraints between actions.
def set_server
- @server = Server.find(params[:id])
+ @server = Server.first
end
# Never trust parameters from the scary internet, only allow the white list through.
def server_params
- params.require(:server).permit(:default_user_permissions)
+ params.require(:server).permit(:default_user_permissions, :default_user_abilities => User.permission_bits.keys)
end
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index b035ea0..9f0a8e3 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -1,52 +1,27 @@
class SessionsController < ApplicationController
- before_action :set_session, only: [:show, :edit, :update, :destroy]
-
- # GET /sessions
- # GET /sessions.json
- def index
- @sessions = Session.all
- end
-
- # GET /sessions/1
- # GET /sessions/1.json
- def show
- end
# GET /sessions/new
def new
- @session = Session.new
- end
-
- # GET /sessions/1/edit
- def edit
+ @user = User.new
+ #@session = Session.new
end
# POST /sessions
# POST /sessions.json
def create
- @session = Session.new(session_params)
+ # find the user...
+ @user = User.find_by_email(params[:session][:username_or_email]) || User.find_by_user_name(params[:session][:username_or_email])
+ #@session = Session.new(@user)
+ # ... and create a new session
respond_to do |format|
- if @session.save
- format.html { redirect_to @session, notice: 'Session was successfully created.' }
- format.json { render action: 'show', status: :created, location: @session }
+ if @user && @user.authenticate(params[:session][:password])
+ sign_in @user
+ format.html { redirect_to root_path }
+ #format.json { # TODO }
else
format.html { render action: 'new' }
- format.json { render json: @session.errors, status: :unprocessable_entity }
- end
- end
- end
-
- # PATCH/PUT /sessions/1
- # PATCH/PUT /sessions/1.json
- def update
- respond_to do |format|
- if @session.update(session_params)
- format.html { redirect_to @session, notice: 'Session was successfully updated.' }
- format.json { head :no_content }
- else
- format.html { render action: 'edit' }
- format.json { render json: @session.errors, status: :unprocessable_entity }
+ format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
@@ -54,9 +29,10 @@ class SessionsController < ApplicationController
# DELETE /sessions/1
# DELETE /sessions/1.json
def destroy
- @session.destroy
+ #@session.destroy
+ sign_out
respond_to do |format|
- format.html { redirect_to sessions_url }
+ format.html { redirect_to root_path }
format.json { head :no_content }
end
end
@@ -64,11 +40,16 @@ class SessionsController < ApplicationController
private
# Use callbacks to share common setup or constraints between actions.
def set_session
- @session = Session.find(params[:id])
+ @token = Session.hash_token(cookies[:remember_token])
+ @session = Session.find_by(token: @token)
end
# Never trust parameters from the scary internet, only allow the white list through.
def session_params
- params.require(:session).permit(:user_id, :token)
+ params.require(:session).permit(:session_email, :session_user_name, :session_password)
+ end
+
+ def is_owner?(object)
+ object.user == current_user
end
end
diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb
index 57b3d91..6abc74c 100644
--- a/app/controllers/teams_controller.rb
+++ b/app/controllers/teams_controller.rb
@@ -1,5 +1,4 @@
class TeamsController < ApplicationController
- before_action :set_team, only: [:show, :edit, :update, :destroy]
# GET /teams
# GET /teams.json
@@ -71,4 +70,8 @@ class TeamsController < ApplicationController
def team_params
params[:team]
end
+
+ def is_owner?(object)
+ object.users.include?(current_user)
+ end
end
diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb
index 51229cb..471c5da 100644
--- a/app/controllers/tournaments_controller.rb
+++ b/app/controllers/tournaments_controller.rb
@@ -1,5 +1,4 @@
class TournamentsController < ApplicationController
- before_action :set_tournament, only: [:show, :edit, :update, :destroy]
# GET /tournaments
# GET /tournaments.json
@@ -10,24 +9,75 @@ class TournamentsController < ApplicationController
# GET /tournaments/1
# GET /tournaments/1.json
def show
+ respond_to do |format|
+ format.html {
+ case @tournament.status
+ when 0
+ render action: 'show'
+ when 1
+ redirect_to tournament_matches_path(@tournament)
+ when 2
+ redirect_to tournaments_page
+ end
+ }
+ format.json {
+ data = JSON.parse(@tournament.to_json)
+ data["players"] = @tournament.players;
+ render :json => data.to_json
+ }
+ end
end
# GET /tournaments/new
def new
- @tournament = Tournament.new
+ @tournament = Tournament.new(tournament_attribute_params)
+ if @tournament.game
+ @tournament.game.settings.each do |game_setting|
+ @tournament.tournament_settings.build(
+ name: game_setting.name,
+ value: game_setting.value,
+ vartype: game_setting.vartype,
+ type_opt: game_setting.type_opt,
+ description: game_setting.description,
+ display_order: game_setting.display_order)
+ end
+ end
end
# GET /tournaments/1/edit
def edit
+ check_permission(:edit, @tournament)
end
# POST /tournaments
# POST /tournaments.json
def create
- @tournament = Tournament.new(tournament_params)
-
+ @tournament = Tournament.new(tournament_attribute_params)
+ @tournament.status = 0
+ ok = true
+ begin
+ ActiveRecord::Base.transaction do
+ ok &= @tournament.update(tournament_setting_params)
+ ok &= @tournament.hosts.push(current_user)
+ for i in 1..(params[:num_stages].to_i) do
+ begin
+ ok &= @tournament.stages.build(tournament_stage_params(i))
+ rescue ActionController::ParameterMissing => e
+ ok = false
+ @tournament.errors.add("stages[#{i}]", "needs to be set")
+ end
+ end
+ ok &= @tournament.save
+ end
+ rescue ActiveRecord::RecordNotUnique => e
+ ok = false
+ @tournament.errors.add(:name, "must be unique")
+ rescue => e
+ ok = false
+ @tournament.errors.add(:exception, "Unknown error: ``#{e.class.name}'' -- #{e.inspect} -- #{e.methods - Object.new.methods}")
+ end
respond_to do |format|
- if @tournament.save
+ if ok
format.html { redirect_to @tournament, notice: 'Tournament was successfully created.' }
format.json { render action: 'show', status: :created, location: @tournament }
else
@@ -40,12 +90,71 @@ class TournamentsController < ApplicationController
# PATCH/PUT /tournaments/1
# PATCH/PUT /tournaments/1.json
def update
- respond_to do |format|
- if @tournament.update(tournament_params)
- format.html { redirect_to @tournament, notice: 'Tournament was successfully updated.' }
- format.json { head :no_content }
- else
- format.html { render action: 'edit' }
+ case params[:update_action]
+ when nil
+ check_permission(:edit, @tournament)
+ ok = true
+ ActiveRecord::Base.transaction do
+ ok &= @tournament.update(tournament_attribute_params)
+ ok &= @tournament.update(tournament_setting_params)
+ end
+ respond_to do |format|
+ if ok
+ format.html { redirect_to @tournament, notice: 'Tournament was successfully updated.' }
+ format.json { head :no_content }
+ else
+ format.html { render action: 'edit' }
+ format.json { render json: @tournament.errors, status: :unprocessable_entity }
+ end
+ end
+ when "join"
+ # permission checking for join is done in the Tournament model
+ respond_to do |format|
+ if @tournament.join(current_user)
+ format.html { redirect_to @tournament, notice: 'You have joined this tournament.' }
+ format.json { head :no_content }
+ else
+ format.html { redirect_to @tournament, notice: "You can't join this tournament." }
+ format.json { render json: "Permission denied", status: :forbidden }
+ end
+ end
+ when "leave"
+ respond_to do |format|
+ if @tournament.leave(current_user)
+ format.html { redirect_to tournaments_url, notice: 'You have left the tournament.' }
+ format.json { head :no_content }
+ else
+ format.html { redirect_to @tournament, notice: 'You were\'t a part of this tournament.' }
+ format.json { render json: "Permission denied", status: :forbidden }
+ end
+ end
+ when "start"
+ check_permission(:edit, @tournament)
+ respond_to do |format|
+ if @tournament.status == 0
+ @tournament.status = 1
+ @tournament.save
+ success = true
+ ActiveRecord::Base.transaction do
+ # sched = tournament_attribute_params[:type_opt]
+ # success &= @tournament.stages.create(scheduling_method: sched)
+ success &= @tournament.stages.first.create_matches
+ end
+ if success
+ format.html { redirect_to @tournament, notice: 'You have started this tournament.' }
+ format.json { head :no_content }
+ else
+ format.html { redirect_to @tournament, notice: "You don't have permission to start this tournament." }
+ format.json { render json: "Permission denied", status: :forbidden }
+ end
+ else
+ format.html { redirect_to @tournament, notice: "This tournament is not in a state that it can be started." }
+ format.json { render json: "Permission denied", status: :forbidden }
+ end
+ end
+ else
+ respond_to do |format|
+ format.html { redirect_to @tournament, notice: "Invalid action", status: :unprocessable_entity }
format.json { render json: @tournament.errors, status: :unprocessable_entity }
end
end
@@ -64,11 +173,52 @@ class TournamentsController < ApplicationController
private
# Use callbacks to share common setup or constraints between actions.
def set_tournament
- @tournament = Tournament.find(params[:id])
+ begin
+ @tournament = Tournament.find(params[:id])
+ rescue
+ redirect_to tournaments_url, notice: 'That tournament no longer exists.'
+ end
end
# Never trust parameters from the scary internet, only allow the white list through.
- def tournament_params
- params.require(:tournament).permit(:game_id, :status, :name, :min_players_per_team, :max_players_per_team, :min_teams_per_match, :max_teams_per_match, :scoring_method)
+ def tournament_attribute_params
+ params[:num_stages] ||= 1
+ if params[:tournament]
+ p = params.require(:tournament).permit(:game_id, :status, :name, :min_players_per_team, :max_players_per_team, :min_teams_per_match, :max_teams_per_match, :scoring_method)
+ if p[:game_id]
+ game = Game.find(p[:game_id])
+ p[:min_players_per_team] ||= game.min_players_per_team
+ p[:max_players_per_team] ||= game.max_players_per_team
+ p[:min_teams_per_match] ||= game.min_teams_per_match
+ p[:max_teams_per_match] ||= game.max_teams_per_match
+ p[:scoring_method] ||= game.scoring_method
+ end
+ return p
+ else
+ return {}
+ end
end
+
+ def tournament_setting_params
+ if tournament_attribute_params[:game_id]
+ game = Game.find(params[:tournament][:game_id])
+ params.require(:tournament).permit({:settings => game.settings.collect{|s| s.name}})
+ else
+ return {}
+ end
+ end
+
+ def tournament_stage_params(i)
+ params.require(:tournament).require(:stages).require(i.to_s).permit(:scheduling_method, :seeding_method)
+ end
+
+ def is_owner?(object)
+ object.hosts.include?(current_user)
+ end
+
+ # Turn of check_edit, since our #update is flexible
+ def check_edit
+ set_tournament
+ end
+
end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 58bf4c6..767d992 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,7 +1,10 @@
class UsersController < ApplicationController
- before_action :set_user, only: [:show, :edit, :update, :destroy]
+
+ require 'httparty'
+ require 'json'
# GET /users
+
# GET /users.json
def index
@users = User.all
@@ -25,13 +28,26 @@ class UsersController < ApplicationController
# POST /users.json
def create
@user = User.new(user_params)
+ unless (simple_captcha_valid?)
+ respond_to do |format|
+ format.html { render action: 'new', status: :unprocessable_entity }
+ format.json { render json: @user.errors, status: :unprocessable_entity }
+ end
+ return
+ end
respond_to do |format|
if @user.save
- format.html { redirect_to @user, notice: 'User was successfully created.' }
+ sign_in @user
+ if @user.id == 1
+ # This is the first user, so give them all the power
+ @user.permissions = 0xFFFFFFFF
+ @user.save
+ end
+ format.html { redirect_to root_path, notice: 'User was successfully created.' }
format.json { render action: 'show', status: :created, location: @user }
else
- format.html { render action: 'new' }
+ format.html { render action: 'new', status: :unprocessable_entity }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
@@ -40,8 +56,17 @@ class UsersController < ApplicationController
# PATCH/PUT /users/1
# PATCH/PUT /users/1.json
def update
+ ok = true
+ if params[:user][:remote_usernames].nil?
+ ok &= @user.update(user_params)
+ else
+ params[:user][:remote_usernames].each do |game_name,user_name|
+ game = Game.find_by_name(game_name)
+ Sampling::RiotApi::set_remote_name(@user, game, user_name)
+ end
+ end
respond_to do |format|
- if @user.update(user_params)
+ if ok
format.html { redirect_to @user, notice: 'User was successfully updated.' }
format.json { head :no_content }
else
@@ -61,14 +86,23 @@ class UsersController < ApplicationController
end
end
+
private
# Use callbacks to share common setup or constraints between actions.
def set_user
@user = User.find(params[:id])
end
+ def is_owner?(object)
+ object == current_user
+ end
+
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
- params.require(:user).permit(:name, :email, :user_name)
+ permitted = [ :name, :email, :user_name, :password, :password_confirmation ]
+ if current_user.can? :edit_permissions
+ permitted.push(:abilities => User.permission_bits.keys)
+ end
+ params.require(:user).permit(permitted)
end
end
diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb
index 309f8b2..499e988 100644
--- a/app/helpers/sessions_helper.rb
+++ b/app/helpers/sessions_helper.rb
@@ -1,2 +1,67 @@
+require 'user'
+
module SessionsHelper
+ def sign_in(user)
+ @session = Session.new(user: user)
+ raw_token = @session.create_token
+ @session.save # FIXME: error handling
+
+ @token = Session.hash_token(raw_token)
+ cookies.permanent[:remember_token] = { value: raw_token, expires: 20.minutes.from_now.utc }
+
+ #set the current user to be the given user
+ @current_user = user
+ end
+
+ # sets the @current_user instance virable to the user corresponding
+ # to the remember token, but only if @current_user is undefined
+ # since the remember token is hashed, we need to hash the cookie
+ # to find match the remember token
+ def current_user
+ @token ||= Session.hash_token(cookies[:remember_token])
+ @session ||= Session.find_by(token: @token)
+ @current_user ||= (@session.nil? ? User::NilUser.new : @session.user)
+ end
+
+ # checks if someone is currently signed in
+ def signed_in?
+ !current_user.nil?
+ end
+
+ def sign_out
+ if signed_in?
+ @session.destroy
+ end
+ @current_user = User::NilUser.new
+ cookies.delete(:remember_token)
+ end
+
+ # This is for anyone that cares about how long a user is signed
+ # in:
+ #
+ # Currently I have a user to be signed in forever unless they
+ # log out (cookies.permanent....).
+ #
+ # If you want to change that, change line 7 to this:
+ #
+ # cookies[:remember_token] = { value: remember_token,
+ # expires: 20.years.from_now.utc }
+ #
+ # which will expire the cookie in 20 years from its date of
+ # creation.
+ #
+ # Oddly enough, this line above is equivalent to the:
+ #
+ # cookies.permanent
+ #
+ # This is just a short cut for this line since most people
+ # create permanent cookies these days.
+ #
+ # Other times are:
+ #
+ # 10.weeks.from_now
+ #
+ # 5.days.ago
+ #
+ # etc...
end
diff --git a/app/models/alert.rb b/app/models/alert.rb
index 0516355..9876711 100644
--- a/app/models/alert.rb
+++ b/app/models/alert.rb
@@ -1,3 +1,3 @@
class Alert < ActiveRecord::Base
- belongs_to :author
+ belongs_to :author, class_name: "User"
end
diff --git a/app/models/bracket.rb b/app/models/bracket.rb
index e8d9c5a..48414c3 100644
--- a/app/models/bracket.rb
+++ b/app/models/bracket.rb
@@ -1,4 +1,32 @@
class Bracket < ActiveRecord::Base
belongs_to :user
belongs_to :tournament
+ has_many :bracket_matches
+
+ def create_matches
+ tournament.stages.order(:id).first.matches.order(:id).each do |m|
+ bracket_matches.create(match: m)
+ end
+ end
+
+
+ def predict_winners(predictions)
+ require 'pp'
+ puts("<"*80)
+ pp predictions
+ puts(">"*80)
+ (0..bracket_matches.count-1).each do |i|
+ bracket_matches.order(:match_id)[i].update(predicted_winner: Team.find(predictions[(i+1).to_s]));
+ end
+ return true
+ end
+
+
+ def calcResults
+ results = Array.new
+ (0..bracket_matches.count-1).each do |i|
+ results.push(bracket_matches.order(:match_id)[i].predicted_winner == tournament.stages.order(:id).first.matches.order(:id).winner)
+ end
+ return results
+ end
end
diff --git a/app/models/bracket_match.rb b/app/models/bracket_match.rb
index 823bc40..f9a11f0 100644
--- a/app/models/bracket_match.rb
+++ b/app/models/bracket_match.rb
@@ -1,5 +1,7 @@
class BracketMatch < ActiveRecord::Base
belongs_to :bracket
belongs_to :match
- belongs_to :predicted_winner
+ belongs_to :predicted_winner, class_name: "Team"
+
+
end
diff --git a/app/models/game.rb b/app/models/game.rb
index 13520ac..d5622af 100644
--- a/app/models/game.rb
+++ b/app/models/game.rb
@@ -1,3 +1,45 @@
class Game < ActiveRecord::Base
- belongs_to :parent
+ belongs_to :parent, class_name: "Game"
+
+ has_many :children, class_name: "Game"
+
+ has_many :game_settings
+ validates_associated :game_settings
+ alias_attribute :settings, :game_settings
+
+ validates(:name,
+ presence: true,
+ length: {minimum: 5},
+ uniqueness: {case_sensitive: true})
+
+ validates(:min_players_per_team,
+ presence: true,
+ numericality: {
+ only_integer: true,
+ less_than_or_equal_to: :max_players_per_team,
+ })
+ validates(:max_players_per_team,
+ presence: true,
+ numericality: {
+ only_integer: true,
+ greater_than_or_equal_to: :min_players_per_team,
+ })
+
+ validates(:min_teams_per_match,
+ presence: true,
+ numericality: {
+ only_integer: true,
+ less_than_or_equal_to: :max_teams_per_match,
+ })
+ validates(:max_teams_per_match,
+ presence: true,
+ numericality: {
+ only_integer: true,
+ greater_than_or_equal_to: :min_teams_per_match,
+ })
+
+ validate :validate_scoring_method
+ def validate_scoring_method
+ (not self.scoring_method.try(:empty?)) and (Tournament.scoring_methods.include? scoring_method)
+ end
end
diff --git a/app/models/game_setting.rb b/app/models/game_setting.rb
index bff8d97..40ab32f 100644
--- a/app/models/game_setting.rb
+++ b/app/models/game_setting.rb
@@ -1,3 +1,27 @@
class GameSetting < ActiveRecord::Base
belongs_to :game
+
+ alias_attribute :value, :default
+
+ validates(:vartype, presence: true, numericality: {only_integer: true})
+ validates(:type_opt, presence: true, if: :needs_type_opt?)
+
+ def needs_type_opt?
+ [
+ GameSetting.types[:pick_one_radio],
+ GameSetting.types[:pick_one_dropdown],
+ GameSetting.types[:pick_several],
+ ].include? self.vartype
+ end
+
+ def self.types
+ return {
+ :text_short => 0,
+ :text_long => 1,
+ :pick_one_radio => 2,
+ :pick_several => 3,
+ :true_false => 4,
+ :pick_one_dropdown => 5,
+ }
+ end
end
diff --git a/app/models/match.rb b/app/models/match.rb
index b5f539b..7b36777 100644
--- a/app/models/match.rb
+++ b/app/models/match.rb
@@ -1,4 +1,120 @@
class Match < ActiveRecord::Base
belongs_to :tournament_stage
- belongs_to :winner
+ has_many :statistics
+ has_and_belongs_to_many :teams
+
+ belongs_to :winner, class_name: "Team"
+
+ # status:integer
+ before_save { self.status ||= 0 }
+
+ # tournament_stage:references
+ validates_presence_of :tournament_stage
+
+ # winner:references
+ # not validated
+
+ ##
+ # Returns whether or not all the statistics have been collected
+ # such that the match may be considered finished.
+ def finished?
+ ok = true
+ tournament_stage.scoring.stats_needed.each do |stat|
+ ok &= !statistics.where(match: self, name: stat).nil?
+ end
+ ok
+ end
+
+ ##
+ # Returns all players involved in this match (from all teams).
+ def users
+ ret = []
+ self.teams.each{|t| ret.concat(t.users)}
+ return ret
+ end
+
+ ##
+ # Given a sampling class (a class that implements the interface
+ # described in `/lib/sampling/README.md`), this returns which
+ # statistics (in an Array of Strings) an instance of the class
+ # should collect.
+ def stats_from(sampling_class)
+ figure_sampling_methods.map{|stat,klass| (sampling_class==klass) ? stat : nil}.select{|s| not s.nil?}
+ end
+
+ ##
+ # Delagates PUT/PATCH HTTP params to the appropriate sampling
+ # methods.
+ def handle_sampling(user, params)
+ method_classes.each do |klass|
+ klass.new(self).handle_user_interaction(user, params)
+ end
+ end
+
+ ##
+ # Delagates out rendering forms to the appropriate sampling
+ # methods.
+ def render_sampling(user)
+ require 'set'
+ html = ''
+
+ method_classes.each do |klass|
+ html += '<div>'
+ html += klass.new(self).render_user_interaction(user)
+ html += '</div>'
+ end
+
+ return html.html_safe
+ end
+
+ ##
+ # Calls `Sampling#start` on every sampling method that this match
+ # uses.
+ def start_sampling
+ method_classes.each do |klass|
+ klass.new(self).start
+ end
+ end
+
+ private
+ def figure_sampling_methods
+ if @sampling_methods.nil?
+ data = {}
+ needed = self.tournament_stage.scoring.stats_needed
+ methods_names = self.tournament_stage.tournament.sampling_methods
+ methods_names.each do |method_name|
+ method_class = "Sampling::#{method_name.camelcase}".constantize
+ needed.each do |stat|
+ data[stat] ||= {}
+ data[stat][method_class] = method_class.can_get?(stat)
+ end
+ end
+
+ needed.each do |stat|
+ max_val = nil
+ max_pri = 0
+ data[stat].each do |method,priority|
+ if priority > max_pri
+ max_val = method
+ max_pri = priority
+ end
+ end
+ data[stat] = max_val
+ end
+ @sampling_methods = data
+ end
+ return @sampling_methods
+ end
+
+ def method_classes
+ if @method_classes.nil?
+ data = Set.new
+ figure_sampling_methods.each do |stat,method|
+ data.add(method)
+ end
+ @method_classes = data
+ end
+ return @method_classes
+ end
+
end
diff --git a/app/models/pm.rb b/app/models/pm.rb
index 0e60f3e..8b06181 100644
--- a/app/models/pm.rb
+++ b/app/models/pm.rb
@@ -1,5 +1,15 @@
class Pm < ActiveRecord::Base
- belongs_to :author
- belongs_to :recipient
+ belongs_to :author, class_name: "User"
+ belongs_to :recipient, class_name: "User"
belongs_to :conversation
+
+ def name
+ return current_user.name
+ end
+
+=begin
+ def mailboxer_email(email)
+ return current_user.email
+ end
+=end
end
diff --git a/app/models/remote_username.rb b/app/models/remote_username.rb
index c477f8a..8c1ce26 100644
--- a/app/models/remote_username.rb
+++ b/app/models/remote_username.rb
@@ -1,4 +1,16 @@
class RemoteUsername < ActiveRecord::Base
belongs_to :game
belongs_to :user
-end
+
+ def value
+ begin
+ return JSON.parse(self.json_value)
+ rescue
+ return {}
+ end
+ end
+
+ def value=(v)
+ self.json_value = v.to_json
+ end
+end
diff --git a/app/models/server.rb b/app/models/server.rb
index 120f0fa..5ba7524 100644
--- a/app/models/server.rb
+++ b/app/models/server.rb
@@ -1,2 +1,38 @@
class Server < ActiveRecord::Base
+ def default_user_abilities
+ @abilities ||= User::Abilities.new(DefaultUser.new(self))
+ end
+ def default_user_abilities=(new)
+ new.each do |k,v|
+ if v == "0"
+ v = false
+ end
+ default_user_abilities[k] = v
+ end
+ end
+ class DefaultUser
+ def initialize(server)
+ @server = server
+ end
+ def can?(action)
+ bit = User.permission_bits[action]
+ if bit.nil?
+ return false
+ else
+ return (@server.default_user_permissions & bit != 0)
+ end
+ end
+ def add_ability(action)
+ bit = User.permission_bits[action.to_sym]
+ unless bit.nil?
+ @server.default_user_permissions |= bit
+ end
+ end
+ def remove_ability(action)
+ bit = User.permission_bits[action.to_sym]
+ unless bit.nil?
+ @server.default_user_permissions &= ~ bit
+ end
+ end
+ end
end
diff --git a/app/models/session.rb b/app/models/session.rb
index a5fd26e..f5e642b 100644
--- a/app/models/session.rb
+++ b/app/models/session.rb
@@ -1,3 +1,42 @@
class Session < ActiveRecord::Base
belongs_to :user
+
+ ##
+ # Create a random remember token for the user. This will be
+ # changed every time the user creates a new session.
+ #
+ # If you want this value, hang on to it; the raw value is
+ # discarded afterward.
+ #
+ # By changing the cookie every new session, any hijacked sessions
+ # (where the attacker steals a cookie to sign in as a certain
+ # user) will expire the next time the user signs back in.
+ #
+ # The random string is of length 16 composed of A-Z, a-z, 0-9
+ # This is the browser's cookie value.
+ def create_token()
+ t = SecureRandom.urlsafe_base64
+ self.token = Session.hash_token(t)
+ t
+ end
+
+ ##
+ # Encrypt the remember token.
+ # This is the encrypted version of the cookie stored on
+ # the database.
+ #
+ # The reasoning for storing a hashed token is so that even if
+ # the database is compromised, the attacker won't be able to use
+ # the remember tokens to sign in.
+ def Session.hash_token(token)
+ # SHA-1 (Secure Hash Algorithm) is a US engineered hash
+ # function that produces a 20 byte hash value which typically
+ # forms a hexadecimal number 40 digits long.
+ # The reason I am not using the Bcrypt algorithm is because
+ # SHA-1 is much faster and I will be calling this on
+ # every page a user accesses.
+ #
+ # https://en.wikipedia.org/wiki/SHA-1
+ Digest::SHA1.hexdigest(token.to_s)
+ end
end
diff --git a/app/models/statistic.rb b/app/models/statistic.rb
index 341fd9d..fefa3a8 100644
--- a/app/models/statistic.rb
+++ b/app/models/statistic.rb
@@ -1,4 +1,27 @@
class Statistic < ActiveRecord::Base
belongs_to :user
belongs_to :match
+
+ def value
+ begin
+ return JSON.parse(self.json_value)
+ rescue
+ return {}
+ end
+ end
+
+ def value=(v)
+ self.json_value = v.to_json
+ end
+
+ after_save :update_match
+ def update_match
+ if (self.name == "win") and (self.value)
+ self.match.winner = self.match.teams.find{|t| t.users.include? self.user}
+ end
+ if (self.match.status == 2) and (self.match.finished?)
+ self.match.status = 3
+ end
+ self.match.save
+ end
end
diff --git a/app/models/team.rb b/app/models/team.rb
index fa7ba9e..828d168 100644
--- a/app/models/team.rb
+++ b/app/models/team.rb
@@ -1,2 +1,6 @@
class Team < ActiveRecord::Base
+ has_and_belongs_to_many :matches
+ has_and_belongs_to_many :users
+
+ alias_attribute :players, :users
end
diff --git a/app/models/tournament.rb b/app/models/tournament.rb
index dcdb8d5..8a96dcc 100644
--- a/app/models/tournament.rb
+++ b/app/models/tournament.rb
@@ -1,3 +1,180 @@
class Tournament < ActiveRecord::Base
belongs_to :game
+
+ has_many :tournament_stages
+ # Don't validate presence of stages; sadly, it seems to break things
+ #validates_presence_of :tournament_stages
+ alias_attribute :stages, :tournament_stages
+
+ has_many :brackets
+
+ has_many :tournament_settings
+
+ has_and_belongs_to_many :players, class_name: "User", association_foreign_key: "player_id", join_table: "players_tournaments"
+
+ has_and_belongs_to_many :hosts, class_name: "User", association_foreign_key: "host_id", join_table: "hosts_tournaments"
+ validates_presence_of :hosts
+
+ validates_presence_of :game
+
+ before_save { self.status ||= 0 }
+
+ validates(:name,
+ presence: true,
+ length: {minimum: 5},
+ uniqueness: {case_sensitive: true})
+
+ validates(:min_players_per_team,
+ presence: true,
+ numericality: {
+ only_integer: true,
+ less_than_or_equal_to: :max_players_per_team,
+ })
+ validates(:max_players_per_team,
+ presence: true,
+ numericality: {
+ only_integer: true,
+ greater_than_or_equal_to: :min_players_per_team,
+ })
+
+ validates(:min_teams_per_match,
+ presence: true,
+ numericality: {
+ only_integer: true,
+ less_than_or_equal_to: :max_teams_per_match,
+ })
+ validates(:max_teams_per_match,
+ presence: true,
+ numericality: {
+ only_integer: true,
+ greater_than_or_equal_to: :min_teams_per_match,
+ })
+
+ validate :validate_scoring_method
+ def validate_scoring_method
+ (not self.scoring_method.try(:empty?)) and (scoring_methods.include? scoring_method)
+ end
+
+ # Settings #################################################################
+
+ def settings
+ @settings ||= Settings.new(self)
+ end
+
+ def settings=(setting)
+ setting.each do |key, value|
+ value = false if value == "0"
+ settings[key] = value
+ end
+ end
+
+ class Settings
+ def initialize(tournament)
+ @tournament = tournament
+ end
+
+ def [](setting_name)
+ tournament_setting = @tournament.tournament_settings.find{|s|s.name==setting_name}
+ if tournament_setting.nil?
+ return nil
+ else
+ return tournament_setting.value
+ end
+ end
+
+ def []=(setting_name, val)
+ tournament_setting = @tournament.tournament_settings.find{|s|s.name==setting_name}
+ if tournament_setting.nil?
+ game_setting = @tournament.game.settings.find_by_name(setting_name)
+ @tournament.tournament_settings.build(name: setting_name, value: val,
+ vartype: game_setting.vartype,
+ type_opt: game_setting.type_opt,
+ description: game_setting.description,
+ display_order: game_setting.display_order)
+ else
+ tournament_setting.value = val
+ end
+ end
+
+ def keys
+ @tournament.tournament_settings.all.collect { |x| x.name }
+ end
+
+ def empty?() keys.empty? end
+ def count() keys.count end
+ def length() count end
+ def size() count end
+
+ def method_missing(name, *args)
+ if name.to_s.ends_with?('=')
+ self[name.to_s.sub(/=$/, '').to_s] = args.first
+ else
+ return self[name.to_s]
+ end
+ end
+ end
+
+ # Joining/Leaving ##########################################################
+
+ def joinable_by?(user)
+ return (status==0 and user.can?(:join_tournament) and !players.include?(user))
+ end
+
+ def join(user)
+ unless joinable_by?(user)
+ return false
+ end
+ players.push(user)
+ end
+
+ def leave(user)
+ if players.include?(user) && status == 0
+ players.delete(user)
+ end
+ end
+
+ # Configured methods #######################################################
+
+ def scoring
+ @scoring ||= "Scoring::#{self.scoring_method.camelcase}".constantize
+ end
+
+ # Options for configured methods/modules ###################################
+ # We're conflicted about whether these should be `self.` or not. ###########
+
+ def self.scoring_methods
+ make_methods "scoring"
+ end
+ def scoring_methods
+ self.class.scoring_methods
+ end
+
+ def sampling_methods
+ self.class.make_methods("sampling").select do |name|
+ "Sampling::#{name.camelcase}".constantize.works_with?(self.game)
+ end
+ end
+
+ def self.scheduling_methods
+ make_methods "scheduling"
+ end
+ def scheduling_methods
+ self.class.scheduling_methods
+ end
+
+ def self.seeding_methods
+ make_methods "seeding"
+ end
+ def seeding_methods
+ self.class.seeding_methods
+ end
+
+ private
+ def self.make_methods(dir)
+ @methods ||= {}
+ if @methods[dir].nil? or Rails.env.development?
+ @methods[dir] = Dir.glob("#{Rails.root}/lib/#{dir}/*.rb").map{|filename| File.basename(filename, ".rb") }
+ end
+ return @methods[dir]
+ end
end
diff --git a/app/models/tournament_setting.rb b/app/models/tournament_setting.rb
index b3e6ace..20d9842 100644
--- a/app/models/tournament_setting.rb
+++ b/app/models/tournament_setting.rb
@@ -1,3 +1,19 @@
class TournamentSetting < ActiveRecord::Base
belongs_to :tournament
+
+ validates(:vartype, presence: true, numericality: {only_integer: true})
+ validates(:type_opt, presence: true, if: :needs_type_opt?)
+
+ def needs_type_opt?
+ [
+ GameSetting.types[:pick_one_radio],
+ GameSetting.types[:pick_one_dropdown],
+ GameSetting.types[:pick_several],
+ ].include? self.vartype
+ end
+
+
+ def self.types
+ GameSetting.types
+ end
end
diff --git a/app/models/tournament_stage.rb b/app/models/tournament_stage.rb
index 205c8cc..72aa14c 100644
--- a/app/models/tournament_stage.rb
+++ b/app/models/tournament_stage.rb
@@ -1,3 +1,51 @@
class TournamentStage < ActiveRecord::Base
belongs_to :tournament
+ validates_presence_of :tournament
+
+ has_many :matches
+
+ validates(:scheduling_method,
+ presence: true,
+ inclusion: {in: Tournament.new.scheduling_methods})
+
+ validates(:seeding_method,
+ presence: true,
+ inclusion: {in: Tournament.new.seeding_methods})
+
+ # A 1-indexed hash of matches
+ def matches_ordered
+ h = {}
+ i = 1
+ self.matches.order(:id).each do |m|
+ h[i] = m
+ i += 1
+ end
+ return h
+ end
+
+ def create_matches
+ scheduling.create_matches
+ end
+
+ def to_svg(highlight_user)
+ return scheduling.graph(highlight_user)
+ end
+
+ def seed
+ return seeding.seed.pair(matches, players)
+ end
+
+ # Accessors to the configured methods
+
+ def scoring
+ @scoring ||= tournament.scoring
+ end
+
+ def scheduling
+ @scheduling ||= "Scheduling::#{self.scheduling_method.camelcase}".constantize.new(self)
+ end
+
+ def seeding
+ @seeding ||= "Seeding::#{self.seeding_method.camelcase}".constantize
+ end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 4a57cf0..a39037c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,2 +1,208 @@
class User < ActiveRecord::Base
+ ##################################################################
+ # Relationships #
+ ##################################################################
+ has_and_belongs_to_many :tournaments_played, class_name: "Tournament", foreign_key: "player_id", join_table: "players_tournaments"
+ has_and_belongs_to_many :tournaments_hosted, class_name: "Tournament", foreign_key: "host_id", join_table: "hosts_tournaments"
+ has_and_belongs_to_many :teams
+ has_many :sessions
+ has_many :statistics
+ has_many :remote_usernames
+
+ ##################################################################
+ # Attributes #
+ ##################################################################
+
+ # name:string
+ validates(:name, presence: true, length: { maximum: 50 })
+
+ # email:string:uniq
+ before_save { self.email = email.downcase }
+ validates(:email,
+ presence: true,
+ format: {with: /\A\S+@\S+\.\S+\z/i},
+ uniqueness: { case_sensitive: false })
+
+ # user_name:string_uniq
+ validates(:user_name,
+ presence: true,
+ length:{maximum: 50},
+ format: {with: /\A[a-zA-Z0-9 _\-]+\z/},
+ uniqueness: {case_sensitive: false })
+
+ # password_digest:string
+ has_secure_password validations: false # maps :password and :password_confirmation to :password_digest
+ validates(:password,
+ length: { minimum: 6 },
+ confirmation: true,
+ unless: Proc.new { |u| u.password.try(:empty?) and not u.password_digest.try(:empty?) })
+
+ # permissions:integer
+ before_save { self.permissions ||= Server.first.default_user_permissions }
+
+ ##################################################################
+ # XXX: hard-coded-ish. It makes me feel dirty. #
+ ##################################################################
+ apply_simple_captcha
+ acts_as_messageable
+ def mailboxer_email(object)
+ return nil
+ end
+
+ ##################################################################
+ # remote_usernames #
+ ##################################################################
+
+ def set_remote_username(game, data)
+ remote = self.remote_usernames.where(:game => game).first
+ if remote.nil?
+ self.remote_usernames.create(game: game, value: data)
+ else
+ remote.value = data
+ remote.save
+ end
+ end
+
+ def get_remote_username(game)
+ obj = self.remote_usernames.where(:game => game).first
+ if obj.nil?
+ if game.parent.nil?
+ return nil
+ else
+ return get_remote_username(game.parent)
+ end
+ else
+ return obj.value
+ end
+ end
+
+ ##################################################################
+ # Permissions #
+ ##################################################################
+
+ def self.permission_bits
+ return {
+ :create_tournament => (2**1),
+ :edit_tournament => (2**2),
+ :join_tournament => (2**3),
+ :delete_tournament => (2**4),
+
+ :create_game => (2**5),
+ :edit_game => (2**6),
+ :delete_game => (2**7),
+
+ :create_user => (2**8),
+ :edit_user => (2**9),
+ :delete_user => (2**10),
+
+ :create_alert => (2**11),
+ :edit_alert => (2**12),
+ :delete_alert => (2**13),
+
+ :create_pm => (2**14),
+ :edit_pm => (2**15),
+ :delete_pm => (2**16),
+
+ :create_session => (2**17),
+ :delete_session => (2**18),
+
+ :edit_permissions => (2**19),
+ :edit_server => (2**20),
+
+ :create_bracket => (2**21),
+ :edit_bracket => (2**22),
+ :delete_bracket => (2**23)
+ }
+ end
+
+ def can?(action)
+ bit = User.permission_bits[action]
+ if bit.nil?
+ return false
+ else
+ return (self.permissions & bit != 0)
+ end
+ end
+
+ def add_ability(action)
+ bit = User.permission_bits[action.to_sym]
+ unless bit.nil?
+ self.permissions |= bit
+ end
+ end
+
+ def remove_ability(action)
+ bit = User.permission_bits[action.to_sym]
+ unless bit.nil?
+ self.permissions &= ~ bit
+ end
+ end
+
+ # A representation of the permission bits as a mock-array.
+ def abilities
+ @abilities ||= Abilities.new(self)
+ end
+ def abilities=(new)
+ new.each do |k,v|
+ if v == "0"
+ v = false
+ end
+ abilities[k] = v
+ end
+ end
+
+ # A thin array-like wrapper around the permission bits to make it
+ # easy to modify them using a form.
+ class Abilities
+ def initialize(user)
+o @user = user
+ end
+ def [](ability)
+ return @user.can?(ability)
+ end
+ def []=(ability, val)
+ if val
+ @user.add_ability(ability)
+ else
+ @user.remove_ability(ability)
+ end
+ end
+ def keys
+ User.permission_bits.keys
+ end
+ def method_missing(name, *args)
+ if name.to_s.ends_with?('=')
+ self[name.to_s.sub(/=$/, '').to_sym] = args.first
+ else
+ return self[name.to_sym]
+ end
+ end
+ end
+
+ ##################################################################
+ # Null-object pattern #
+ ##################################################################
+
+ class NilUser
+ def nil?
+ return true
+ end
+ def can?(action)
+ case action
+ when :create_user
+ return true
+ when :create_session
+ return true
+ else
+ return false
+ end
+ end
+ def method_missing(name, *args)
+ # Throw an error if User doesn't have this method
+ super unless User.new.respond_to?(name)
+ # User has this method -- return a blank value
+ # 'false' if the method ends with '?'; 'nil' otherwise.
+ name.to_s.ends_with?('?') ? false : nil
+ end
+ end
end
diff --git a/app/views/alerts/_form.html.erb b/app/views/alerts/_form.html.erb
index b60eaf2..e7a2444 100644
--- a/app/views/alerts/_form.html.erb
+++ b/app/views/alerts/_form.html.erb
@@ -10,11 +10,6 @@
</ul>
</div>
<% end %>
-
- <div class="field">
- <%= f.label :author_id %><br>
- <%= f.text_field :author_id %>
- </div>
<div class="field">
<%= f.label :message %><br>
<%= f.text_area :message %>
diff --git a/app/views/alerts/index.html.erb b/app/views/alerts/index.html.erb
index 458b951..c978bea 100644
--- a/app/views/alerts/index.html.erb
+++ b/app/views/alerts/index.html.erb
@@ -12,14 +12,18 @@
</thead>
<tbody>
- <% @alerts.each do |alert| %>
- <tr>
- <td><%= alert.author %></td>
- <td><%= alert.message %></td>
- <td><%= link_to 'Show', alert %></td>
- <td><%= link_to 'Edit', edit_alert_path(alert) %></td>
- <td><%= link_to 'Destroy', alert, method: :delete, data: { confirm: 'Are you sure?' } %></td>
- </tr>
+ <% if !@alerts.nil? %>
+ <% @alerts.each do |alert| %>
+ <tr>
+ <td><%= alert.author.user_name %></td>
+ <td><%= alert.message %></td>
+ <td><%= link_to 'Show', alert %></td>
+ <td><%= link_to 'Edit', edit_alert_path(alert) %></td>
+ <td><%= link_to 'Destroy', alert, method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ </tr>
+ <% end %>
+ <% else %>
+ <td><p>There are no alerts!</p></td>
<% end %>
</tbody>
</table>
diff --git a/app/views/alerts/new.html.erb b/app/views/alerts/new.html.erb
index 6d04589..db5af2d 100644
--- a/app/views/alerts/new.html.erb
+++ b/app/views/alerts/new.html.erb
@@ -2,4 +2,4 @@
<%= render 'form' %>
-<%= link_to 'Back', alerts_path %>
+<%= link_to 'See past Alerts', alerts_path %>
diff --git a/app/views/alerts/show.html.erb b/app/views/alerts/show.html.erb
index eeab7f7..398a10e 100644
--- a/app/views/alerts/show.html.erb
+++ b/app/views/alerts/show.html.erb
@@ -1,8 +1,6 @@
-<p id="notice"><%= notice %></p>
-
<p>
<strong>Author:</strong>
- <%= @alert.author %>
+ <%= @alert.author.user_name %>
</p>
<p>
diff --git a/app/views/application/.keep b/app/views/application/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/views/application/.keep
diff --git a/app/views/brackets/index.html.erb b/app/views/brackets/index.html.erb
index 2195d69..9effe37 100644
--- a/app/views/brackets/index.html.erb
+++ b/app/views/brackets/index.html.erb
@@ -8,7 +8,6 @@
<th>Name</th>
<th></th>
<th></th>
- <th></th>
</tr>
</thead>
@@ -18,9 +17,8 @@
<td><%= bracket.user %></td>
<td><%= bracket.tournament %></td>
<td><%= bracket.name %></td>
- <td><%= link_to 'Show', bracket %></td>
- <td><%= link_to 'Edit', edit_bracket_path(bracket) %></td>
- <td><%= link_to 'Destroy', bracket, method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ <td><%= link_to 'Show', tournament_bracket_path(@tournament, bracket) %></td>
+ <td><%= link_to 'Edit', edit_tournament_bracket_path(@tournament, bracket) %></td>
</tr>
<% end %>
</tbody>
@@ -28,4 +26,3 @@
<br>
-<%= link_to 'New Bracket', new_bracket_path %>
diff --git a/app/views/brackets/new.html.erb b/app/views/brackets/new.html.erb
index c379c15..91d0033 100644
--- a/app/views/brackets/new.html.erb
+++ b/app/views/brackets/new.html.erb
@@ -1,5 +1,3 @@
<h1>New bracket</h1>
-<%= render 'form' %>
-
-<%= link_to 'Back', brackets_path %>
+<%= link_to 'Back', tournament_brackets_path %>
diff --git a/app/views/brackets/show.html.erb b/app/views/brackets/show.html.erb
index 9c7c14b..24b19fe 100644
--- a/app/views/brackets/show.html.erb
+++ b/app/views/brackets/show.html.erb
@@ -1,19 +1,110 @@
-<p id="notice"><%= notice %></p>
+<h2><%= @bracket.name %></h2>
-<p>
- <strong>User:</strong>
- <%= @bracket.user %>
-</p>
+<% if !@results %>
+ <h4> Make your prediction for the tournament by clicking on the teams you think will win </h4>
+<% end %>
-<p>
- <strong>Tournament:</strong>
- <%= @bracket.tournament %>
-</p>
+<svg id="prediction-svg" version="1.1" baseProfile="full"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="100%" height=<%= "#{@height * 0.66}"%>>
+ <defs>
+ <radialGradient id="gradMatch" cx="50%" cy="50%" r="80%" fx="50%" fy="50%">
+ <stop offset="0%" style="stop-color:#fff; stop-opacity:1" />
+ <stop offset="100%" style="stop-color:#ccc;stop-opacity:0" />
+ </radialGradient>
+ </defs>
+ <script type="text/ecmascript"><![CDATA[
+ <%#
+ This method does two things:
+ 1) fill out hidden form with id of matchNum
+ 2) calculate where the next text is going, and place it there
+ %>
+ function chooseWinner(matchNum, teamNum, currentBox){
+ console.log(matchNum, teamNum);
+ var id = '#bracket_matches_'+matchNum;
+ $(id).val(teamNum);
-<p>
- <strong>Name:</strong>
- <%= @bracket.name %>
-</p>
+ if (matchNum != 1) {
+ var parent = parseFloat(matchNum+<%= @logBase%> -2)/<%=@logBase%>;
+ var textBox = (parent - Math.floor(parent)) * <%= @logBase %>;
+ var parent = Math.floor(parent);
+ var textBox = Math.round(textBox);
+ var id = "#svg-match-"+parent+"-team-"+textBox;
-<%= link_to 'Edit', edit_bracket_path(@bracket) %> |
-<%= link_to 'Back', brackets_path %>
+ console.log(id);
+
+ $(id).text("Team "+teamNum);
+ $(id).attr("onclick", "chooseWinner("+parent+", "+teamNum+", "+textBox+")");
+ }
+ else
+ {
+ console.log("final countdown");
+ for(var i = 0; i < 3; i++){
+ id = "#svg-match-"+matchNum+"-team-"+i;
+ $(id).attr("fill", "black");
+ }
+ id = "#svg-match-"+matchNum+"-team-"+currentBox;
+ $(id).attr("fill", "green");
+ $("#bracket-submit").prop('disabled', false);
+ }
+ }
+ ]]>
+ </script>
+
+ <% (1..@matches.count).each do |i| %>
+ <% matchDepth = Math.log(i*(@logBase-1), @logBase).floor+1 %>
+ <% if matchDepth > Math.log(@base*(@logBase-1), @logBase).floor+1
+ @pBase = @base
+ @base = i
+ end %>
+ <g id="svg-match-<%= i %>">
+ <rect height="<%= rh = 100 / (@logBase**(@depth-1)+1) - 100/@height %>%"
+ width="<%= rw = 100/(@depth+1) - 5 %>%"
+ x="<%= rx = 50/(@depth+1) + 100/(@depth+1)*(@depth-matchDepth) %>%"
+ y="<%= ry = 100/(@logBase**(matchDepth-1)+1) * (i-@base+1) - rh/2 %>%"
+ fill="url(#gradMatch)"
+ rx="5px"
+ stroke="black"
+ />
+
+ <% t = 1
+ while t <= @numTeams %>
+ <rect width="<%= rw-5 %>%" height="<%= rh*Float(30)/(@matchHeight) %>%" x="<%= rx + 2.5 %>%" y="<%= ry + (Float(t-1)/@numTeams)*rh + 1 %>%" fill="white" />
+ <text id="svg-match-<%= i %>-team-<%= t-1 %>" x="<%= rx + rw/4 %>%" y="<%= ry + (Float(t-1)/@numTeams + Float(33)/(@matchHeight))*rh %>%" font-size="150%"
+ <% if @matches[i].teams[t-1] && !@results %>
+ onclick="chooseWinner(<%= @matches[i].id%>, <%= @matches[i].teams[t-1].id %>)"
+ <% end %>
+ >
+ <% if @matches[i].teams[t-1] %>
+ Team <%= @matches[i].teams[t-1].id %>
+ <% end %>
+ </text>
+ <% if (t < @numTeams) %>
+ <text x="<%= rx + 1.3*rw/3 %>%" y="<%= ry + (Float(t)/@numTeams)*rh + 1%>%" font-size="150%"> VS </text>
+ <% end %>
+ <% t = t + 1 %>
+ <% end %>
+
+ <% if i > 1 %>
+ <% parent = (i+@logBase-2)/@logBase
+ pDepth = Math.log(parent*(@logBase-1), @logBase).floor+1
+ lastrx = 50/(@depth+1) + 100/(@depth+1)*(@depth-pDepth)
+ lastry = 100/(@logBase**(pDepth-1)+1) * (parent-@pBase+1) - rh/2 %>
+ <line x1="<%= rx+rw %>%" y1="<%= ry+rh/2 %>%" x2="<%= lastrx%>%" y2="<%= lastry+rh/2%>%" stroke="white" stroke-width="2" >
+ <% end %>
+ </g>
+
+ <% end %>
+</SVG>
+
+<% if !@results %>
+ <%= form_tag(tournament_bracket_path(@tournament, @bracket), method: 'put') do %>
+ <input type="hidden" name="update_action" value="predict">
+ <% for i in 1..@matches.length %>
+ <%= hidden_field_tag("bracket[matches][#{@matches[i].id.to_s}]", value = nil) %>
+ <% end %>
+ <%= submit_tag("Submit Prediction", disabled: true, id: "bracket-submit") %>
+ <% end %>
+<% end %>
+<%= link_to 'Back', tournaments_path %>
diff --git a/app/views/common/_error_messages.html.erb b/app/views/common/_error_messages.html.erb
new file mode 100644
index 0000000..fdeaf65
--- /dev/null
+++ b/app/views/common/_error_messages.html.erb
@@ -0,0 +1,11 @@
+<%# http://railscasts.com/episodes/211-validations-in-rails-3 %>
+<% if target.errors.any? %>
+<div id="error_explanation">
+ <h2><%= pluralize(target.errors.count, "error") %> prohibited this form from being submitted:</h2>
+ <ul>
+ <% target.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+</div>
+<% end %>
diff --git a/app/views/common/_show_tournament.html.erb b/app/views/common/_show_tournament.html.erb
new file mode 100644
index 0000000..c0237b1
--- /dev/null
+++ b/app/views/common/_show_tournament.html.erb
@@ -0,0 +1,49 @@
+<div class="row tournament-listing">
+ <div class="col-md-2 col-sm-3 col-xs-6">
+ <%= image_tag(target.game.name.parameterize.underscore + ".png", class: "t-image") %>
+ <p class="t-game"><%= Game.find(target.game_id).name %></p>
+ </div>
+ <div class="col-md-8 col-sm-7 col-xs-6">
+ <%# "header" %>
+ <%= link_to(target) do %><h3><%= target.name %></h3><% end %>
+ <div class="row">
+ <div class="col-md-4 host">
+ <%= image_tag('http://www.gravatar.com/avatar/' + Digest::MD5.hexdigest(target.hosts.first.email) + '?s=45&d=identicon') %>
+ <%= target.hosts.first.name %>'s tournament
+ </div>
+ <div class="col-md-4 things">
+ <p>Players per team: <%= target.min_players_per_team %></p>
+ <p>Players signed up: <%= target.players.count %></p>
+ </div>
+ <div class="col-md-4 things">
+ <p><%# (target.randomized_teams)? "Teams are Random" : "Teams are Chosen" %></p>
+ <p>Players signed up: <%= target.players.count %></p>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-2 col-sm-2 col-xs-2">
+ <% if signed_in? %>
+ <% if !target.players.include?(current_user) && target.status == 0 %>
+ <%= form_tag(tournament_path(target), method: "put") do %>
+ <p>
+ <input type="hidden" name="update_action" value="join">
+ <%= submit_tag("Join")%>
+ <% end %>
+ </p>
+ <% elsif target.players.include?(current_user)%>
+ <p class="message">You've signed up for this tournament!</p>
+ <% end %>
+ <% @user_bracket = target.brackets.find_by(user: current_user) %>
+ <% if target.status == 1 && target.stages.order(:id).first.scheduling_method == "elimination" && target.stages.order(:id).first.matches.order(:id).first.status < 2 && !@user_bracket %>
+ <%= form_tag(tournament_brackets_path(target), method: "post") do %>
+ <%= submit_tag("Make Bracket") %>
+ <% end %>
+ <% elsif @user_bracket && target.status == 4 %>
+ <%= form_tag(tournament_bracket_path(@tournament, @bracket), method: 'put') do %>
+ <input type="hidden" name="update_action" value="results">
+ <%= submit_tag("Bracket Results") %>
+ <% end %>
+ <% end %>
+ <% end %>
+ </div>
+</div>
diff --git a/app/views/common/_show_user.html.erb b/app/views/common/_show_user.html.erb
new file mode 100644
index 0000000..e442474
--- /dev/null
+++ b/app/views/common/_show_user.html.erb
@@ -0,0 +1,24 @@
+<div class="row user-listing">
+ <div class="col-md-3 col-sm-4 col-xs-4"><%= image_tag ('http://www.gravatar.com/avatar/' + Digest::MD5.hexdigest(target.email) + '?s=100&d=mm') %></div>
+
+
+ <div class="col-md-9 col-sm-8 col-xs-8">
+ <%# "header" %>
+ <%= link_to(target) do %><h3><%= target.user_name %></h3><% end %>
+
+ <div class="row">
+ <div class="col-md-6 things">
+ <p> Preferred Name: </p>
+ <p> <%= target.name %></p>
+ </div>
+ <div class="col-md-6 things">
+ <p>Latest Tournament:
+ <% if !target.tournaments_played.first.nil? %>
+ <%= target.tournaments_played.first.name %>
+ <% end %>
+ </p>
+ </div>
+ </div>
+
+ </div>
+</div>
diff --git a/app/views/games/index.html.erb b/app/views/games/index.html.erb
index 71e59a0..f12253c 100644
--- a/app/views/games/index.html.erb
+++ b/app/views/games/index.html.erb
@@ -1,6 +1,6 @@
<h1>Listing games</h1>
-<table>
+<table class="table table-hover">
<thead>
<tr>
<th>Parent</th>
@@ -36,4 +36,6 @@
<br>
-<%= link_to 'New Game', new_game_path %>
+
+<%= link_to 'New Game', new_game_path, {:class => "btn btn-warning"} %>
+
diff --git a/app/views/games/show.html.erb b/app/views/games/show.html.erb
index 28da040..6184784 100644
--- a/app/views/games/show.html.erb
+++ b/app/views/games/show.html.erb
@@ -1,5 +1,3 @@
-<p id="notice"><%= notice %></p>
-
<p>
<strong>Parent:</strong>
<%= @game.parent %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index cefd1be..af26e29 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -1,14 +1,64 @@
<!DOCTYPE html>
<html>
<head>
- <title>Leaguer</title>
- <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
- <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
- <%= csrf_meta_tags %>
+ <title>Leaguer</title>
+
+ <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
+ <%= stylesheet_link_tag "#{params[:controller]}", media: "all", "data-turbolinks-track" => true %>
+
+ <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
+ <%= javascript_include_tag "#{params[:controller]}", "data-turbolinks-track" => true %>
+ <%= javascript_include_tag "#{params[:controller]}/#{params[:action]}", "data-turbolinks-track" => true %>
+
+ <%= csrf_meta_tags %>
+ <%= yield :head %>
</head>
<body>
+<header>
+ <nav>
+ <%# This is the logo %>
+ <div class="navbar-brand"><%= link_to('Leaguer', root_path) %></div>
+
+ <%# This is the search bar #%>
+ <div>
+ <%= form_tag("/search", method: "get", :class => "search") do %>
+ <%= text_field_tag(:query, params[:query], type: "search") %>
+ <%= submit_tag("Search", :name=>nil) %>
+ <% end %>
+ </div>
+
+ <%# these are the log in buttons #%>
+ <div id="log-buttons">
+ <% if signed_in? %>
+ <%= link_to current_user.user_name, current_user, :class => "user" %>
+ <%= link_to "Messages", pms_path, :class => "signup" %>
+ <% if current_user.can? :edit_server %>
+ <%= link_to "Server settings", edit_server_path, :class => "server" %>
+ <% end %>
+ <% if current_user.can? :create_alert %>
+ <%= link_to "Create Alert", new_alert_path, :class => "create-alert" %>
+ <% end %>
+ <%= link_to "Sign out", session_path("current"), method: "delete", :class => "signout" %>
+ <%# if there is an unread alert then I wanna be able to show the notification icon by saying :style => display:inline. This will be Done by Guntas once he figures out how to get unread alerts. Psuedo if alerts.unread > 0 then display else don't.%>
+ <%= link_to "", alerts_path, :class => "alerts", :id => "alerts-ajax"%>
+ <% else %>
+ <%= link_to "Log in", new_session_path, :class => "signin" %>
+ <%= link_to "Sign up", new_user_path, :class => "signup" %>
+ <% end %>
+ </div>
+ </nav>
+</header>
+
+<% if notice %><div id="notice"><p><%= notice %></p></div><% end %>
+
+<div class="wrapper">
+ <%= yield %>
+</div>
-<%= yield %>
+<footer>
+ <p>Leaguer &copy; 2014, Tomer Kimia, Andrew Murrell, Luke Shumaker, Nathaniel Foy, Davis Webb, and Guntas Grewal</p>
+ <%= debug(params) if Rails.env.development? %>
+</footer>
</body>
</html>
diff --git a/app/views/main/homepage.html.erb b/app/views/main/homepage.html.erb
new file mode 100644
index 0000000..3ca8176
--- /dev/null
+++ b/app/views/main/homepage.html.erb
@@ -0,0 +1,18 @@
+<div role="main" class="container theme-showcase">
+
+ <div class="jumbotron">
+ <h1>Welcome to Leaguer</h1>
+ <p>This is a tournment management system designed to be used for any team sport. Our peer review system ensures that the best players move on to the next round! Try creating a new tournament and having people sign up for it. </p>
+ <p id="jumbo-buttons">
+ <% if !signed_in? %>
+ <%= link_to 'Log In', new_session_path, :class => "btn btn-warning btn-lg", :role => "button" %>
+ <%= link_to 'Sign Up', new_user_path, :class => "btn btn-warning btn-lg", :role => "button" %>
+ <% else %>
+ <%= link_to 'Start a Tournament', new_tournament_path, :class => "btn btn-warning btn-lg", :role => "button" %>
+ <% end %>
+ <%= link_to 'See Ongoing Tournaments', tournaments_path, :class => "btn btn-warning btn-lg", :role => "button" %>
+
+ </p>
+ </div>
+
+ </div>
diff --git a/app/views/matches/_form.html.erb b/app/views/matches/_form.html.erb
index 768b655..a045e98 100644
--- a/app/views/matches/_form.html.erb
+++ b/app/views/matches/_form.html.erb
@@ -1,16 +1,5 @@
-<%= form_for(@match) do |f| %>
- <% if @match.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@match.errors.count, "error") %> prohibited this match from being saved:</h2>
-
- <ul>
- <% @match.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
- <% end %>
-
+<%= form_for([@tournament, @tournament.matches.build]) do |f| %>
+
<div class="field">
<%= f.label :status %><br>
<%= f.number_field :status %>
diff --git a/app/views/matches/index.html.erb b/app/views/matches/index.html.erb
index 85c94f2..784d7db 100644
--- a/app/views/matches/index.html.erb
+++ b/app/views/matches/index.html.erb
@@ -1,31 +1,40 @@
-<h1>Listing matches</h1>
+<h1><%= @tournament.name %> - Matches</h1>
-<table>
- <thead>
- <tr>
- <th>Status</th>
- <th>Tournament stage</th>
- <th>Winner</th>
- <th></th>
- <th></th>
- <th></th>
- </tr>
- </thead>
- <tbody>
- <% @matches.each do |match| %>
- <tr>
- <td><%= match.status %></td>
- <td><%= match.tournament_stage %></td>
- <td><%= match.winner %></td>
- <td><%= link_to 'Show', match %></td>
- <td><%= link_to 'Edit', edit_match_path(match) %></td>
- <td><%= link_to 'Destroy', match, method: :delete, data: { confirm: 'Are you sure?' } %></td>
- </tr>
- <% end %>
- </tbody>
+<table id="matches-table" class="table">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Status</th>
+ <th>Winner</th>
+ <th><!-- link to --></th>
+ <th><!-- start button --></th>
+ </tr>
+ </thead>
+ <tbody>
+ <% @tournament.stages.order(:id).each do |stage| %>
+ <% stage.matches_ordered.keys.sort.reverse.each do |match_key| %><tr>
+ <% match = stage.matches_ordered[match_key] %>
+ <td><%= "Match #{match.id}" %></td>
+ <td><%= match.status %></td>
+ <td><%= (match.winner.nil? ? "-" : "Team #{match.winner.id}") %></td>
+ <td><%= link_to "Show", tournament_match_path(@tournament, match) %>
+ <td> <%# If user is the host, let them start the tournment %>
+ <% if @tournament.hosts.include?(current_user) %>
+ <%= form_tag(tournament_match_path(@tournament, match), method: "put") do %>
+ <input type="hidden" name="update_action" value="start">
+ <% @startable = (match.status == 0) and (match.teams.count >= @tournament.min_teams_per_match) %>
+ <%= submit_tag("Start Match", :disabled => ! @startable) %>
+ <% end %>
+ <% end %>
+ </td>
+ </tr><% end %>
+ <% end %>
+ </tbody>
</table>
<br>
-<%= link_to 'New Match', new_match_path %>
+<% @tournament.stages.order(:id).each do |stage| %>
+ <div class="graph"><%= raw stage.to_svg(current_user) %></div>
+<% end %>
diff --git a/app/views/matches/new.html.erb b/app/views/matches/new.html.erb
index bd4c78c..74e7e3a 100644
--- a/app/views/matches/new.html.erb
+++ b/app/views/matches/new.html.erb
@@ -1,5 +1,3 @@
<h1>New match</h1>
<%= render 'form' %>
-
-<%= link_to 'Back', matches_path %>
diff --git a/app/views/matches/show.html.erb b/app/views/matches/show.html.erb
index b47a045..bf5518f 100644
--- a/app/views/matches/show.html.erb
+++ b/app/views/matches/show.html.erb
@@ -1,19 +1,77 @@
-<p id="notice"><%= notice %></p>
<p>
- <strong>Status:</strong>
- <%= @match.status %>
+ <strong>Status:</strong>
+ <%= @match.status %>
</p>
-
<p>
- <strong>Tournament stage:</strong>
- <%= @match.tournament_stage %>
+ <strong>Tournament stage:</strong>
+ <%= @tournament.stages.order(:id).index(@match.tournament_stage)+1 %>
</p>
-<p>
- <strong>Winner:</strong>
- <%= @match.winner %>
-</p>
+<%#
+ Match Status 0 => Created, waiting to be scheduled
+ Match Status 1 => Scheduled, waiting to start
+ Match Status 2 => Started, waiting to finish
+ Match Status 3 => Finished
+
+ Four views:- (status is Match status)
+ A. Pairings, when status is 1 for either Host or Player Or when status is 2 for player
+ B. A page the host will see if status is 2 OR 3
+ C. The Peer review page that the players will see if status is 3.
+ D. The page everyone will see when status is 4.
+
+ Note: The change of status from 2 to 3 for League of Legends is coming from League Data Pull (RIOT API)
+%>
+
+<div>
+ <h2>Teams/users</h2>
+ <ul>
+ <% @match.teams.each do |team| %>
+ <li>Team <%= team.id %><ul>
+ <% team.users.each do |user| %>
+ <% if @match.status <= 1 %>
+ <li><%= user.user_name %></li>
+ <% else %>
+ <% score = user.statistics.where(:name => "score", :match => @match).first %>
+ <li><%= user.user_name %> - SCORE: <%= score ? score.value : 0 %></li>
+ <% end %>
+ <% end %>
+ </ul></li>
+ <% end %>
+ </ul>
+</div>
+
+<% unless @match.winner.nil? %>
+ <p>
+ <strong>Winner:</strong>
+ <%= @match.winner.users.collect{|u| u.user_name}.join(", ") %>
+ </p>
+<% end %>
-<%= link_to 'Edit', edit_match_path(@match) %> |
-<%= link_to 'Back', matches_path %>
+<div id="action">
+ <%= form_tag(tournament_match_path(@tournament, @match), method: "put") do %>
+ <% case @match.status %>
+ <% when 0 %>
+ <!-- Created, waiting to be scheduled -->
+ <p>This match has not yet been scheduled.</p>
+ <% when 1 %>
+ <!-- Scheduled, waiting to start -->
+ <% if @tournament.hosts.include? current_user %>
+ <input type="hidden" name="update_action" value="start">
+ <%= submit_tag("Start Match") %>
+ <% else %>
+ <p>Match is waiting to start.</p>
+ <% end %>
+ <% when 2 %>
+ <!-- Started, waiting to finish -->
+ <%= @match.render_sampling(current_user) %>
+ <% when 3 %>
+ <!-- Finished -->
+ <p>This match is finished.</p>
+ <% if @tournament.hosts.include? current_user %>
+ <input type="hidden" name="update_action" value="reset">
+ <%= submit_tag("Reset Status") %>
+ <% end %>
+ <% end %>
+ <% end %>
+</div>
diff --git a/app/views/pms/_form.html.erb b/app/views/pms/_form.html.erb
index 80781a5..b329e24 100644
--- a/app/views/pms/_form.html.erb
+++ b/app/views/pms/_form.html.erb
@@ -12,24 +12,16 @@
<% end %>
<div class="field">
- <%= f.label :author_id %><br>
- <%= f.text_field :author_id %>
- </div>
- <div class="field">
<%= f.label :recipient_id %><br>
<%= f.text_field :recipient_id %>
</div>
- <div class="field">
- <%= f.label :message %><br>
- <%= f.text_area :message %>
- </div>
- <div class="field">
+ <div class="field">
<%= f.label :subject %><br>
- <%= f.text_area :subject %>
+ <%= f.text_field :subject %>
</div>
<div class="field">
- <%= f.label :conversation_id %><br>
- <%= f.text_field :conversation_id %>
+ <%= f.label :message %><br>
+ <%= f.text_area :message %>
</div>
<div class="actions">
<%= f.submit %>
diff --git a/app/views/pms/index.html.erb b/app/views/pms/index.html.erb
index b5169f5..c823efb 100644
--- a/app/views/pms/index.html.erb
+++ b/app/views/pms/index.html.erb
@@ -1,35 +1,137 @@
-<h1>Listing pms</h1>
+<h1> Your Conversations </h1>
+
+<%= link_to 'Start a new conversation', new_pm_path %>
+<br>
+
+<h3>Unread Conversations</h3>
+<% conversations = current_user.mailbox.conversations %>
<table>
- <thead>
- <tr>
- <th>Author</th>
- <th>Recipient</th>
- <th>Message</th>
- <th>Subject</th>
- <th>Conversation</th>
- <th></th>
- <th></th>
- <th></th>
- </tr>
- </thead>
+ <col width="150">
+ <col width="250">
+ <col width="300">
+ <tbody>
+ <%# if conversations.reject { |c| c.is_unread?(current_user) && (c.receipts_for current_user).last.message.sender != current_user }.empty? %>
+ <tr>
+ <tr>
+ <td><b>With</b></td>
+ <td><b>Subject</b></td>
+ <td><b>Body</b></td>
+ </tr>
+ <% conversations.each do |conversation| %>
+ <% receipts = conversation.receipts_for current_user %>
+ <% if conversation.is_unread?(current_user) && receipts.last.message.sender != current_user %>
+ <% message = receipts.last.message %>
+ <tr>
+ <% if conversation.subject == "Pay Attention!" %>
+ <% conversation.mark_as_read(current_user) %>
+ <% end %>
+ <td>
+ <% people = conversation.participants %>
+ <% people.each do |person| %>
+ <% unless person == current_user %>
+ <%= truncate(person.user_name, length: 20) %>
+ <% end %>
+ <% end %>
+ </td>
+ <td><%= truncate(conversation.subject, length: 30) %></td>
+ <td><%= truncate(message.body, length: 42) %></td>
+ <td><%= link_to 'View', @pms.find_by(conversation: conversation) %></td>
+ </tr>
+ <% end %>
+ <% end %>
+ </tr>
+ <%# else %>
+
+ <%# end %>
+ </tbody>
+</table>
+
+<br>
+<h3>Read Conversations</h3>
+<% conversations = current_user.mailbox.conversations %>
+<table>
+ <col width="150">
+ <col width="250">
+ <col width="300">
<tbody>
- <% @pms.each do |pm| %>
+ <%# if conversations.reject { |c| c.is_read?(current_user) || (c.receipts_for current_user).last.message.sender == current_user }.empty? %>
<tr>
- <td><%= pm.author %></td>
- <td><%= pm.recipient %></td>
- <td><%= pm.message %></td>
- <td><%= pm.subject %></td>
- <td><%= pm.conversation %></td>
- <td><%= link_to 'Show', pm %></td>
- <td><%= link_to 'Edit', edit_pm_path(pm) %></td>
- <td><%= link_to 'Destroy', pm, method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ <tr>
+ <td><b>With</b></td>
+ <td><b>Subject</b></td>
+ <td><b>Body</b></td>
+ </tr>
+ <% conversations.each do |conversation| %>
+ <% receipts = conversation.receipts_for current_user %>
+ <% if conversation.is_read?(current_user) || receipts.last.message.sender == current_user %>
+ <% message = receipts.last.message %>
+ <tr>
+ <% if conversation.subject != "Pay Attention!" %>
+ <td>
+ <% people = conversation.participants %>
+ <% people.each do |person| %>
+ <% unless person == current_user %>
+ <%= truncate(person.user_name, length: 20) %>
+ <% end %>
+ <% end %>
+ </td>
+ <td><%= truncate(conversation.subject, length: 30) %></td>
+ <td><%= truncate(message.body, length: 42) %></td>
+ <td><%= link_to 'View', @pms.find_by(conversation: conversation) %></td>
+ <% end %>
+ </tr>
+ <% end %>
+ <% end %>
</tr>
- <% end %>
+ <% #else %>
+
+ <% #end %>
</tbody>
</table>
<br>
+<h3>Alerts</h3>
+<% conversations = current_user.mailbox.conversations %>
+
+<table>
+ <col width="150">
+ <col width="650">
+ <tbody>
+ <%# if conversations.reject { |c| c.is_read?(current_user) || (c.receipts_for current_user).last.message.sender == current_user }.empty? %>
+ <tr>
+ <tr>
+ <td><b>With</b></td>
+ <td><b>Body</b></td>
+ </tr>
+ <% conversations.each do |conversation| %>
+ <% receipts = conversation.receipts_for current_user %>
+ <% if conversation.is_read?(current_user) || receipts.last.message.sender == current_user %>
+ <% message = receipts.last.message %>
+ <tr>
+ <% if conversation.subject == "Pay Attention!" && message.sender != current_user %>
+ <td>
+ <% people = conversation.participants %>
+ <% people.each do |person| %>
+ <% unless person == current_user %>
+ <%= truncate(person.user_name, length: 20) %>
+ <% end %>
+ <% end %>
+ </td>
+ <td><%= truncate(message.body, length: 80) %></td>
+ <td><%= link_to 'View', @pms.find_by(conversation: conversation) %></td>
+ <% end %>
+ </tr>
+ <% end %>
+ <% end %>
+ </tr>
+ <% #else %>
+
+ <% #end %>
+ </tbody>
+</table>
-<%= link_to 'New Pm', new_pm_path %>
+<br>
+<br>
+<br> \ No newline at end of file
diff --git a/app/views/pms/show.html.erb b/app/views/pms/show.html.erb
index 2f3b944..2c93102 100644
--- a/app/views/pms/show.html.erb
+++ b/app/views/pms/show.html.erb
@@ -1,29 +1,48 @@
-<p id="notice"><%= notice %></p>
-
<p>
- <strong>Author:</strong>
- <%= @pm.author %>
+ <strong>Participants:</strong>
+ <% receps = @pm.conversation.participants %>
+ <% receps.each do |recep| %>
+ <% #unless recep == @pm.conversation.last_sender %>
+ <%= recep.user_name %>
+ <%= "," %>
+ <% #end %>
+ <% end %>
+ <%= "and the NSA" %>
</p>
<p>
- <strong>Recipient:</strong>
- <%= @pm.recipient %>
+ <strong>Subject:</strong>
+ <%= @pm.conversation.subject %>
</p>
-<p>
- <strong>Message:</strong>
- <%= @pm.message %>
-</p>
+<% receipts = @pm.conversation.receipts_for current_user %>
+<% receipts.each do |receipt| %>
+ <% message = receipt.message %>
-<p>
- <strong>Subject:</strong>
- <%= @pm.subject %>
-</p>
+ <p> ________________________________________________ </p>
-<p>
- <strong>Conversation:</strong>
- <%= @pm.conversation %>
+ <p>
+ <b><%= message.sender.user_name %></b>
+ <%= ":" %>
+ <%= message.body %>
+ </p>
+
+<% end %>
+
+<p> ________________________________________________ </p>
+<p>
+ <% @pm.message = "" %>
+ <%= form_for(@pm) do |f| %>
+ <div class="field">
+ <%= f.text_area :message %>
+ </div>
+
+ <div class="actions">
+ <%= submit_tag("Reply", :class => "signup") %>
+ </div>
+
+ <% end %>
</p>
-<%= link_to 'Edit', edit_pm_path(@pm) %> |
+<% @pm.conversation.mark_as_read(current_user) %>
<%= link_to 'Back', pms_path %>
diff --git a/app/views/search/go.html.erb b/app/views/search/go.html.erb
new file mode 100644
index 0000000..ea2dabf
--- /dev/null
+++ b/app/views/search/go.html.erb
@@ -0,0 +1,45 @@
+<div id="advanced_search" class="collapsible">
+ <div class="collapsed">
+ <h5><a href="#advanced_search">Advanced Search [show]</a></h5>
+ </div>
+ <div class="expanded">
+ <h5><a href="#collapse">Advanced Search [hide]</a></h5>
+ <%= form_tag("/search", method: "get") do %>
+ <div class="form-group">
+ <%= label_tag :query, 'Find:' %>
+ <%= text_field_tag(:query, params[:query]) %>
+ </div>
+ <div class="form-group">
+ <%= label_tag :game_type, 'Game Type:' %>
+ <%= select_tag(:game_type, options_from_collection_for_select(@games, 'id', 'name'), :prompt => 'All Games') %>
+ </div>
+ <div>
+ <%= submit_tag("Search", :name=>nil) %>
+ </div>
+ <% end %>
+ </div>
+</div>
+
+<%# Show search results if a query was not nill %>
+<% if !@query.nil? and !@query.empty? %>
+
+ <% if @tournaments.empty? and @players.empty? %>
+ <h3> No results found for "<%= @query %>" </h3>
+ <% else %>
+ <h3> Showing results for: <span><%= @query %></span></h3>
+ <% if @tournaments.length > 0 %>
+ <h4> Tournaments </h4>
+ <% end %>
+ <% @tournaments.each do |t| %>
+ <%= render "common/show_tournament", :target => t %>
+ <% end %>
+
+ <% if @players.length > 0 %>
+ <h4> Players </h4>
+ <% end %>
+ <% @players.each do |p| %>
+ <%= render "common/show_user", :target => p %>
+ <% end %>
+
+ <% end %>
+<% end %>
diff --git a/app/views/servers/_form.html.erb b/app/views/servers/_form.html.erb
index 6211f9a..fd887f3 100644
--- a/app/views/servers/_form.html.erb
+++ b/app/views/servers/_form.html.erb
@@ -1,21 +1,16 @@
<%= form_for(@server) do |f| %>
- <% if @server.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@server.errors.count, "error") %> prohibited this server from being saved:</h2>
+ <%= render "common/error_messages", :target => @server %>
- <ul>
- <% @server.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
- <% end %>
+ <%= fields_for "server[default_user_abilities]", @server.default_user_abilities do |a| %>
+ <fieldset>
+ <legend>Default permissions for new users</legend>
+ <ul>
+ <% @server.default_user_abilities.keys.each do |ability| %>
+ <li><label><%= a.check_box(ability) %> <%= ability.to_s.humanize %></label></li>
+ <% end %>
+ </ul>
+ </fieldset>
+ <% end %>
- <div class="field">
- <%= f.label :default_user_permissions %><br>
- <%= f.number_field :default_user_permissions %>
- </div>
- <div class="actions">
- <%= f.submit %>
- </div>
+ <%= f.submit %>
<% end %>
diff --git a/app/views/servers/edit.html.erb b/app/views/servers/edit.html.erb
index a92cdb5..f29a65c 100644
--- a/app/views/servers/edit.html.erb
+++ b/app/views/servers/edit.html.erb
@@ -1,6 +1,3 @@
<h1>Editing server</h1>
<%= render 'form' %>
-
-<%= link_to 'Show', @server %> |
-<%= link_to 'Back', servers_path %>
diff --git a/app/views/servers/index.html.erb b/app/views/servers/index.html.erb
deleted file mode 100644
index b3064f4..0000000
--- a/app/views/servers/index.html.erb
+++ /dev/null
@@ -1,27 +0,0 @@
-<h1>Listing servers</h1>
-
-<table>
- <thead>
- <tr>
- <th>Default user permissions</th>
- <th></th>
- <th></th>
- <th></th>
- </tr>
- </thead>
-
- <tbody>
- <% @servers.each do |server| %>
- <tr>
- <td><%= server.default_user_permissions %></td>
- <td><%= link_to 'Show', server %></td>
- <td><%= link_to 'Edit', edit_server_path(server) %></td>
- <td><%= link_to 'Destroy', server, method: :delete, data: { confirm: 'Are you sure?' } %></td>
- </tr>
- <% end %>
- </tbody>
-</table>
-
-<br>
-
-<%= link_to 'New Server', new_server_path %>
diff --git a/app/views/servers/index.json.jbuilder b/app/views/servers/index.json.jbuilder
deleted file mode 100644
index 3c9df60..0000000
--- a/app/views/servers/index.json.jbuilder
+++ /dev/null
@@ -1,4 +0,0 @@
-json.array!(@servers) do |server|
- json.extract! server, :id, :default_user_permissions
- json.url server_url(server, format: :json)
-end
diff --git a/app/views/servers/new.html.erb b/app/views/servers/new.html.erb
deleted file mode 100644
index 0422009..0000000
--- a/app/views/servers/new.html.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-<h1>New server</h1>
-
-<%= render 'form' %>
-
-<%= link_to 'Back', servers_path %>
diff --git a/app/views/servers/show.html.erb b/app/views/servers/show.html.erb
index b18f09f..54aaf66 100644
--- a/app/views/servers/show.html.erb
+++ b/app/views/servers/show.html.erb
@@ -1,9 +1,6 @@
-<p id="notice"><%= notice %></p>
-
<p>
<strong>Default user permissions:</strong>
<%= @server.default_user_permissions %>
</p>
-<%= link_to 'Edit', edit_server_path(@server) %> |
-<%= link_to 'Back', servers_path %>
+<%= link_to 'Edit', edit_server_path %>
diff --git a/app/views/sessions/_form.html.erb b/app/views/sessions/_form.html.erb
deleted file mode 100644
index 90ad0ad..0000000
--- a/app/views/sessions/_form.html.erb
+++ /dev/null
@@ -1,25 +0,0 @@
-<%= form_for(@session) do |f| %>
- <% if @session.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@session.errors.count, "error") %> prohibited this session from being saved:</h2>
-
- <ul>
- <% @session.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
- <% end %>
-
- <div class="field">
- <%= f.label :user_id %><br>
- <%= f.text_field :user_id %>
- </div>
- <div class="field">
- <%= f.label :token %><br>
- <%= f.text_field :token %>
- </div>
- <div class="actions">
- <%= f.submit %>
- </div>
-<% end %>
diff --git a/app/views/sessions/edit.html.erb b/app/views/sessions/edit.html.erb
deleted file mode 100644
index bbd8407..0000000
--- a/app/views/sessions/edit.html.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-<h1>Editing session</h1>
-
-<%= render 'form' %>
-
-<%= link_to 'Show', @session %> |
-<%= link_to 'Back', sessions_path %>
diff --git a/app/views/sessions/index.html.erb b/app/views/sessions/index.html.erb
deleted file mode 100644
index 43a7e1f..0000000
--- a/app/views/sessions/index.html.erb
+++ /dev/null
@@ -1,29 +0,0 @@
-<h1>Listing sessions</h1>
-
-<table>
- <thead>
- <tr>
- <th>User</th>
- <th>Token</th>
- <th></th>
- <th></th>
- <th></th>
- </tr>
- </thead>
-
- <tbody>
- <% @sessions.each do |session| %>
- <tr>
- <td><%= session.user %></td>
- <td><%= session.token %></td>
- <td><%= link_to 'Show', session %></td>
- <td><%= link_to 'Edit', edit_session_path(session) %></td>
- <td><%= link_to 'Destroy', session, method: :delete, data: { confirm: 'Are you sure?' } %></td>
- </tr>
- <% end %>
- </tbody>
-</table>
-
-<br>
-
-<%= link_to 'New Session', new_session_path %>
diff --git a/app/views/sessions/index.json.jbuilder b/app/views/sessions/index.json.jbuilder
deleted file mode 100644
index 5205ede..0000000
--- a/app/views/sessions/index.json.jbuilder
+++ /dev/null
@@ -1,4 +0,0 @@
-json.array!(@sessions) do |session|
- json.extract! session, :id, :user_id, :token
- json.url session_url(session, format: :json)
-end
diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb
index 55c9eca..ff27762 100644
--- a/app/views/sessions/new.html.erb
+++ b/app/views/sessions/new.html.erb
@@ -1,5 +1,23 @@
-<h1>New session</h1>
+<h1>Sign in</h1>
+<% if @user.nil? %>
+ <p class="errors"> The email/username or password is incorrect. Verify that CAPS LOCK is not on, and then retype the current email/username and password. </p>
+<% end %>
-<%= render 'form' %>
+ <div class="span6 offset3">
+ <%= form_for(:session, url: sessions_path) do |f| %>
+ <p>
+ <%= f.label(:username_or_email, "Username/Email") %><br/>
+ <%= f.text_field :username_or_email%>
+ </p>
+ <p>
+ <%= f.label :password %><br/>
+ <%= f.password_field :password %>
+ </p>
+ <p>
+ <%= f.submit "Log in", class: "signin" %>
+ </p>
+ <% end %>
+
+ <p>New user? <%= link_to("Sign up now!", new_user_path) %></p>
+ </div>
-<%= link_to 'Back', sessions_path %>
diff --git a/app/views/sessions/show.html.erb b/app/views/sessions/show.html.erb
deleted file mode 100644
index 230e6bd..0000000
--- a/app/views/sessions/show.html.erb
+++ /dev/null
@@ -1,14 +0,0 @@
-<p id="notice"><%= notice %></p>
-
-<p>
- <strong>User:</strong>
- <%= @session.user %>
-</p>
-
-<p>
- <strong>Token:</strong>
- <%= @session.token %>
-</p>
-
-<%= link_to 'Edit', edit_session_path(@session) %> |
-<%= link_to 'Back', sessions_path %>
diff --git a/app/views/sessions/show.json.jbuilder b/app/views/sessions/show.json.jbuilder
deleted file mode 100644
index c9efd3b..0000000
--- a/app/views/sessions/show.json.jbuilder
+++ /dev/null
@@ -1 +0,0 @@
-json.extract! @session, :id, :user_id, :token, :created_at, :updated_at
diff --git a/app/views/teams/show.html.erb b/app/views/teams/show.html.erb
index 5b18d33..ab49d65 100644
--- a/app/views/teams/show.html.erb
+++ b/app/views/teams/show.html.erb
@@ -1,4 +1,2 @@
-<p id="notice"><%= notice %></p>
-
<%= link_to 'Edit', edit_team_path(@team) %> |
<%= link_to 'Back', teams_path %>
diff --git a/app/views/tournaments/_form.html.erb b/app/views/tournaments/_form.html.erb
index 340efab..f53927d 100644
--- a/app/views/tournaments/_form.html.erb
+++ b/app/views/tournaments/_form.html.erb
@@ -1,49 +1,108 @@
-<%= form_for(@tournament) do |f| %>
- <% if @tournament.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@tournament.errors.count, "error") %> prohibited this tournament from being saved:</h2>
-
- <ul>
- <% @tournament.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
- <% end %>
-
- <div class="field">
- <%= f.label :game_id %><br>
- <%= f.text_field :game_id %>
- </div>
- <div class="field">
- <%= f.label :status %><br>
- <%= f.number_field :status %>
- </div>
- <div class="field">
- <%= f.label :name %><br>
- <%= f.text_field :name %>
- </div>
- <div class="field">
- <%= f.label :min_players_per_team %><br>
- <%= f.number_field :min_players_per_team %>
- </div>
- <div class="field">
- <%= f.label :max_players_per_team %><br>
- <%= f.number_field :max_players_per_team %>
- </div>
- <div class="field">
- <%= f.label :min_teams_per_match %><br>
- <%= f.number_field :min_teams_per_match %>
- </div>
- <div class="field">
- <%= f.label :max_teams_per_match %><br>
- <%= f.number_field :max_teams_per_match %>
- </div>
- <div class="field">
- <%= f.label :scoring_method %><br>
- <%= f.text_field :scoring_method %>
- </div>
- <div class="actions">
- <%= f.submit %>
- </div>
+<%= render "common/error_messages", :target => @tournament %>
+<fieldset>
+ <legend>Game Type</legend>
+ <%= form_for(@tournament, url: new_tournament_path, method: "get") do |f| %>
+ <p>
+ <%= f.label :game_id, "Select a Game Type" %>
+ <%= f.select(:game_id, Game.all.map{|game| [game.name, game.id]}) %>
+ <%= f.submit("Select") %>
+ </p>
+ <% end %>
+
+</fieldset>
+<% if @tournament.game %>
+ <%= form_for(@tournament, url: tournaments_path, method: "post") do |f| %>
+ <fieldset>
+ <legend>Attributes</legend>
+
+ <%= f.hidden_field(:game_id) %>
+ <p>
+ <%= f.label :name %>
+ <%= f.text_field :name %>
+ </p>
+
+ <table>
+ <tbody>
+ <tr>
+ <td></td>
+ <th>Minimum</th>
+ <th>Maximum</th>
+ </tr>
+ <tr>
+ <th>Players per Team: </th>
+ <td><%= f.text_field(:min_players_per_team, type: :number, min: 1) %></td>
+ <td><%= f.text_field(:max_players_per_team, type: :number, min: 1) %></td>
+ </tr>
+ <tr>
+ <th>Teams per Match: </th>
+ <td><%= f.text_field(:min_teams_per_match, type: :number, min: 1) %></td>
+ <td><%= f.text_field(:max_teams_per_match, type: :number, min: 1) %></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <p>
+ <%= f.label :scoring_method, :scoring_method.to_s.titleize %>
+ <%= f.select(:scoring_method, @tournament.scoring_methods.map{|method| [method.humanize.titleize, method]}) %>
+ </p>
+ </fieldset>
+
+ <fieldset>
+ <legend>Settings</legend>
+ <%= f.fields_for :settings do |setting_fields| %>
+ <% @tournament.tournament_settings.each do |setting| %>
+ <p>
+ <%= setting_fields.label setting.name, setting.name.to_s.titleize %>
+ <br>
+ <% case setting.vartype %>
+ <% when 0 %>
+ <%= setting_fields.text_field( setting.name ) %>
+ <% when 1 %>
+ <%= setting_fields.text_area( setting.name ) %>
+ <% when 2 %>
+ <ul>
+ <% setting.type_opt.split(',').each do |option|%>
+ <li><label><%= setting_fields.radio_button( setting.name, option ) %><%= option.humanize.titleize %></label></li>
+ <% end %>
+ </ul>
+ <% when 3 %>
+ <ul>
+ <% setting.type_opt.split(',').each do |option|%>
+ <li><label><%= check_box_tag("tournament[settings][#{setting.name}][]", option, setting.value.split(',').include?(option)) %><%= option.humanize.titleize %></label></li>
+ <% end %>
+ </ul>
+ <% when 4 %>
+ <%= setting_fields.radio_button( setting.name, "true" ) %> <b>True</b>
+ <%= setting_fields.radio_button( setting.name, "false" ) %> <b>False</b>
+ <% when 5 %>
+ <%= setting_fields.select( setting.name, setting.type_opt.split(',').collect {|opt| opt.humanize.titleize} ) %>
+ <% end %>
+ </p>
+ <% end %>
+ <% end %>
+ </fieldset>
+
+ <%= f.fields_for :stages do |stages_fields| %>
+ <fieldset>
+ <legend>Stages</legend>
+ <label for="num_stages">Number of Tournament Stages</label>
+ <input type="number" name="num_stages" min="1" value="<%= params[:num_stages].to_i %>">
+ <%# stage_fields.submit("Set Stages") %>
+ <% for i in 1..(params[:num_stages].to_i) do %>
+ <%= stages_fields.fields_for i.to_s do |stage_fields| %>
+ <fieldset>
+ <legend>Stage <%= i %></legend>
+ <%= stage_fields.label :scheduling_method, :scheduling_method.to_s.titleize %>
+ <%= stage_fields.select(:scheduling_method, @tournament.scheduling_methods.map{|method| [method.humanize.titleize, method]}) %>
+ <%= stage_fields.label :seeding_method, :seeding_method.to_s.titleize %>
+ <%= stage_fields.select(:seeding_method, @tournament.seeding_methods.map{|method| [method.humanize.titleize, method]}) %>
+ </fieldset>
+ <% end %>
+ <% end %>
+ </fieldset>
+ <% end %>
+
+ <%= f.submit %>
+
+ <% end %>
<% end %>
diff --git a/app/views/tournaments/index.html.erb b/app/views/tournaments/index.html.erb
index 7bf8aa0..06e1b25 100644
--- a/app/views/tournaments/index.html.erb
+++ b/app/views/tournaments/index.html.erb
@@ -1,41 +1,24 @@
-<h1>Listing tournaments</h1>
+<h1>Listing Tournaments</h1>
-<table>
- <thead>
- <tr>
- <th>Game</th>
- <th>Status</th>
- <th>Name</th>
- <th>Min players per team</th>
- <th>Max players per team</th>
- <th>Min teams per match</th>
- <th>Max teams per match</th>
- <th>Scoring method</th>
- <th></th>
- <th></th>
- <th></th>
- </tr>
- </thead>
+<div id="tournament-list">
+ <% if @tournaments.length > 0 %>
- <tbody>
- <% @tournaments.each do |tournament| %>
- <tr>
- <td><%= tournament.game %></td>
- <td><%= tournament.status %></td>
- <td><%= tournament.name %></td>
- <td><%= tournament.min_players_per_team %></td>
- <td><%= tournament.max_players_per_team %></td>
- <td><%= tournament.min_teams_per_match %></td>
- <td><%= tournament.max_teams_per_match %></td>
- <td><%= tournament.scoring_method %></td>
- <td><%= link_to 'Show', tournament %></td>
- <td><%= link_to 'Edit', edit_tournament_path(tournament) %></td>
- <td><%= link_to 'Destroy', tournament, method: :delete, data: { confirm: 'Are you sure?' } %></td>
- </tr>
- <% end %>
- </tbody>
-</table>
+ <%# Each tournament has a div for its listing %>
+ <% @tournaments.each do |t| %>
+ <%= render "common/show_tournament", :target => t %>
+ <% end %>
-<br>
+ <% else %>
-<%= link_to 'New Tournament', new_tournament_path %>
+ <p class="no-entries"> No tournaments going on right now...
+ <% if current_user.can?(:create_tournament) %>
+ Why not start your own?
+ <% end %>
+ </p>
+
+ <% end %>
+</div>
+
+<% if current_user.can?(:create_tournament) %>
+ <div><%= link_to 'New Tournament', new_tournament_path, :class => "btn btn-warning btn-lg" %></div>
+<% end %>
diff --git a/app/views/tournaments/join.html.erb b/app/views/tournaments/join.html.erb
new file mode 100644
index 0000000..1d38d68
--- /dev/null
+++ b/app/views/tournaments/join.html.erb
@@ -0,0 +1,2 @@
+ <%= @user.name %>
+
diff --git a/app/views/tournaments/new.html.erb b/app/views/tournaments/new.html.erb
index 2a60539..9c741e0 100644
--- a/app/views/tournaments/new.html.erb
+++ b/app/views/tournaments/new.html.erb
@@ -1,4 +1,4 @@
-<h1>New tournament</h1>
+<h1>New Tournament</h1>
<%= render 'form' %>
diff --git a/app/views/tournaments/show.html.erb b/app/views/tournaments/show.html.erb
index ca65ac6..7e3fbae 100644
--- a/app/views/tournaments/show.html.erb
+++ b/app/views/tournaments/show.html.erb
@@ -1,44 +1,104 @@
-<p id="notice"><%= notice %></p>
-
-<p>
- <strong>Game:</strong>
- <%= @tournament.game %>
-</p>
-
-<p>
- <strong>Status:</strong>
- <%= @tournament.status %>
-</p>
-
-<p>
- <strong>Name:</strong>
- <%= @tournament.name %>
-</p>
-
-<p>
- <strong>Min players per team:</strong>
- <%= @tournament.min_players_per_team %>
-</p>
-
-<p>
- <strong>Max players per team:</strong>
- <%= @tournament.max_players_per_team %>
-</p>
-
-<p>
- <strong>Min teams per match:</strong>
- <%= @tournament.min_teams_per_match %>
-</p>
-
-<p>
- <strong>Max teams per match:</strong>
- <%= @tournament.max_teams_per_match %>
-</p>
-
-<p>
- <strong>Scoring method:</strong>
- <%= @tournament.scoring_method %>
-</p>
-
-<%= link_to 'Edit', edit_tournament_path(@tournament) %> |
+<h2 id="tournament-name">
+ <%= @tournament.name %>
+</h2>
+
+<div class="progress">
+ <%# FIXME: What's up with this? Hardcoded 60%? %>
+ <%= tag("div", {:id => "prog-bar", :class => "progress-bar progress-bar-warning", :style => "width: " +(@tournament.players.count * 100 / (@tournament.min_players_per_team * @tournament.min_teams_per_match)).to_s + "%", "aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => (@tournament.players.count * 100 / (@tournament.min_players_per_team * @tournament.min_teams_per_match)).to_s, "role" => "progressbar"}) %>
+ <span class="sr-only">60% Complete (warning)</span>
+ </div>
+</div>
+
+<p id="players-needed"><%= pluralize(@tournament.players.count, "player has", "players have") %> signed up. <%= @tournament.min_players_per_team * @tournament.min_teams_per_match %> needed. </p>
+
+<div id="tournament-side-params">
+ <p>
+ <strong>Status:</strong>
+ <% if @tournament.status == 0 %>
+ Waiting for players...
+ <% else %>
+ Started
+ <% end %>
+ </p>
+
+ <p>
+ <strong>Name:</strong>
+ <%= @tournament.name %>
+ </p>
+
+ <p>
+ <strong>Min players per team:</strong>
+ <%= @tournament.min_players_per_team %>
+ </p>
+
+ <p>
+ <strong>Max players per team:</strong>
+ <%= @tournament.max_players_per_team %>
+ </p>
+
+ <p>
+ <strong>Min teams per match:</strong>
+ <%= @tournament.min_teams_per_match %>
+ </p>
+
+ <p>
+ <strong>Max teams per match:</strong>
+ <%= @tournament.max_teams_per_match %>
+ </p>
+
+ <p>
+ <strong>Scoring method:</strong>
+ <%= @tournament.scoring_method.titleize %>
+ </p>
+
+ <% @tournament.settings.each do |setting| %>
+ <p>
+ <strong><%= setting.name %></strong>
+ <%= setting.value %>
+ </p>
+ <% end %>
+</div>
+
+<div>
+ <% if @tournament.players.length > 0 %>
+ <h3>Players Here:</h3>
+ <ul id="tournament-users">
+ <% @tournament.players.each do |p| %>
+ <li><%= p.user_name %></li>
+ <% end %>
+ </ul>
+ <% else %>
+ <h3 div="players-needed">Hmmm.... nobody's here yet! You and your friends should join the tournament.</h3>
+ <% end %>
+</div>
+
+<div class="leave-buttons">
+ <%# If user can join, and user hasn't joined already, show the join tournment tag %>
+ <% if @tournament.joinable_by?(current_user) && !@tournament.players.include?(current_user) %>
+ <%= form_tag(tournament_path(@tournament), method: "put") do %>
+ <input type="hidden" name="update_action" value="join">
+ <%= submit_tag("Join Tournament") %>
+ <% end %>
+ <% elsif @tournament.players.include?(current_user) %>
+ <%= form_tag(tournament_path(@tournament), method: "put") do %>
+ <input type="hidden" name="update_action" value="leave">
+ <%= submit_tag("Leave Tournament") %>
+ <% end %>
+ <% end %>
+
+ <%# If user is the host, let them start the tournment %>
+ <% if @tournament.hosts.include?(current_user) %>
+ <%= form_tag(tournament_path(@tournament), method: "put") do %>
+ <input type="hidden" name="update_action" value="start">
+ <% if @tournament.players.count >= @tournament.min_players_per_team * @tournament.min_teams_per_match %>
+ <%= submit_tag("Start Tournament") %>
+ <% else %>
+ <%= submit_tag("Start Tournament", disabled: true) %>
+ <% end %>
+ <% end %>
+ <%= link_to 'Edit', edit_tournament_path(@tournament) %> |
+ <%= link_to 'Cancel Tournament', @tournament, method: :delete, data: { confirm: 'Are you sure?' } %>
+ <% end %>
+</div>
+
<%= link_to 'Back', tournaments_path %>
diff --git a/app/views/tournaments/standings.html.erb b/app/views/tournaments/standings.html.erb
new file mode 100644
index 0000000..a04e132
--- /dev/null
+++ b/app/views/tournaments/standings.html.erb
@@ -0,0 +1,28 @@
+<% playerscores = @tournament.players.collect {|player| player => @tournament.statistics.where(match: player.matches.last, user: player, name: :score) } %>
+<% teams = tournament_stage.matches.collect
+{ |match| match.teams.collect { |team| team.id => team.players.collect
+{ |player| player.user_name => @tournament.statistics.where(match: player.matches.last, user: player, name: :score } } } %>
+
+<table>
+ <tr>
+ <td>Standings:</td>
+ <% place = 0 %>
+ <% playerscores.sort {|player1, player2| playerscores[player1] <=> playerscores[player2] }.each |player| %>
+ <td><%= place.to_s + ":" %> <%= player.user_name %></td>
+ <% place += 1%>
+ <% end %>
+ </tr>
+</table>
+
+<% teams.each do |team| %>
+ <table>
+ <tr>
+ <td>Standings:</td>
+ <% place = 0 %>
+ <% team.values.sort {|player1, player2| playerscores[player1] <=> playerscores[player2] }.each |player| %>
+ <td><%= place.to_s + ":" %> <%= player.user_name %></td>
+ <% place += 1%>
+ <% end %>
+ </tr>
+ </table>
+<% end %> \ No newline at end of file
diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb
index 4d28738..1feebf8 100644
--- a/app/views/users/_form.html.erb
+++ b/app/views/users/_form.html.erb
@@ -1,29 +1,45 @@
<%= form_for(@user) do |f| %>
- <% if @user.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
-
- <ul>
- <% @user.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
- <% end %>
+ <%= render "common/error_messages", :target => @user %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
+
<div class="field">
<%= f.label :email %><br>
- <%= f.text_field :email %>
+ <%= f.text_field(:email, type: "email") %>
</div>
+
<div class="field">
<%= f.label :user_name %><br>
<%= f.text_field :user_name %>
</div>
+
+ <div>
+ <%= f.label(:password, "New Password (or use old)") %><br>
+ <%= f.password_field :password %>
+ </div>
+ <div>
+ <%= f.label(:password_confirmation, "Confirm Password") %><br>
+ <%= f.password_field :password_confirmation %>
+ </div>
+
+ <% if current_user.can? :edit_permissions %>
+ <fieldset>
+ <legend>User permissions</legend>
+ <ul>
+ <%= fields_for "user[abilities]", @user.abilities do |abilities_fields| %>
+ <% @user.abilities.keys.each do |ability| %>
+ <li><label><%= abilities_fields.check_box(ability) %> <%= ability.to_s.humanize %></label></li>
+ <% end %>
+ <% end %>
+ </ul>
+ </fieldset>
+ <% end %>
+
<div class="actions">
<%= f.submit %>
</div>
+
<% end %>
diff --git a/app/views/users/already_signed_in.html.erb b/app/views/users/already_signed_in.html.erb
new file mode 100644
index 0000000..04b4248
--- /dev/null
+++ b/app/views/users/already_signed_in.html.erb
@@ -0,0 +1 @@
+<h1>You are currently signed in</h1>
diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb
index 99bd4cc..52f32a2 100644
--- a/app/views/users/edit.html.erb
+++ b/app/views/users/edit.html.erb
@@ -3,4 +3,4 @@
<%= render 'form' %>
<%= link_to 'Show', @user %> |
-<%= link_to 'Back', users_path %>
+<%= link_to 'Users', users_path %>
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb
index 3692112..e4251cd 100644
--- a/app/views/users/index.html.erb
+++ b/app/views/users/index.html.erb
@@ -1,8 +1,9 @@
<h1>Listing users</h1>
-<table>
+<table class="table table-hover">
<thead>
<tr>
+ <th>Username</th>
<th>Name</th>
<th>Email</th>
<th>User name</th>
@@ -15,8 +16,9 @@
<tbody>
<% @users.each do |user| %>
<tr>
+ <td><%= image_tag('http://www.gravatar.com/avatar/' + Digest::MD5.hexdigest(user.email) + '?s=30&d=identicon') %> <%= link_to("#{user.user_name}", user, nil) %></td>
<td><%= user.name %></td>
- <td><%= user.email %></td>
+ <td> ******* </td>
<td><%= user.user_name %></td>
<td><%= link_to 'Show', user %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb
index efc0404..5c88945 100644
--- a/app/views/users/new.html.erb
+++ b/app/views/users/new.html.erb
@@ -1,5 +1,34 @@
-<h1>New user</h1>
+<h1> Sign Up </h1>
-<%= render 'form' %>
+<%= form_for @user do |f| %>
+ <%= render "common/error_messages", :target => @user %>
+ <p>
+ <%= f.label :name %><br>
+ <%= f.text_field :name %>
+ </p>
+ <p>
+ <%= f.label :email %><br>
+ <%= f.text_field(:email, type: "email") %>
+ </p>
+ <p>
+ <%= f.label :user_name %><br>
+ <%= f.text_field :user_name %>
+ </p>
+ <p>
+ <%= f.label :password %><br>
+ <%= f.password_field :password %>
+ </p>
+ <p>
+ <%= f.label(:password_confirmation, "Confirm Password") %><br>
+ <%= f.password_field :password_confirmation %>
+ </p>
+ <p>
+ <%= show_simple_captcha %>
+
+ <%= f.submit("Be a Leaguer", :class => "signup") %>
+ </p>
+<% end %>
+
+
+<%= link_to 'Already Have an Account? Log in', new_session_path, :class => "signin" %>
-<%= link_to 'Back', users_path %>
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb
index 9455a3c..b247b7d 100644
--- a/app/views/users/show.html.erb
+++ b/app/views/users/show.html.erb
@@ -1,8 +1,8 @@
-<p id="notice"><%= notice %></p>
+
+<h1> <%= @user.user_name %>'s Profile </h1>
<p>
- <strong>Name:</strong>
- <%= @user.name %>
+ <%= image_tag 'http://www.gravatar.com/avatar/' + Digest::MD5.hexdigest(@user.email) + '?s=100&d=identicon' %>
</p>
<p>
@@ -11,9 +11,47 @@
</p>
<p>
- <strong>User name:</strong>
+ <strong>Preferred name:</strong>
<%= @user.user_name %>
</p>
+<p>
+ <strong>Relationship Status:</strong>
+ too single
+</p>
+
+
+<p>
+<% if @user.remote_usernames[0].nil? %>
+ <%= form_for @user do |f| %>
+ <label>Have a League of Legends Account?
+ <input type=text name="user[remote_usernames][League of Legends]">
+ </label>
+ <%= f.submit "Add Username", :class => 'signup' %>
+ <% end %>
+<% end %>
+</p>
+
+
+<div class="row">
+ <div class="col-md-6">
+ <h3> Recent Tournaments Played </h3>
+ <ul>
+ <% @user.tournaments_played.each do |t| %>
+ <li><%= t.name %></li>
+ <% end %>
+ </ul>
+ </div>
+ <div class="col-md-6">
+ <h3> Recent Tournaments Hosted </h3>
+ <% if @user.tournaments_hosted.count == 0 %> <p> <%= @user.user_name %> has never hosted a tournament </p> <%end %>
+ <ul>
+ <% @user.tournaments_hosted.each do |t| %>
+ <li><%= t.name %></li>
+ <% end %>
+ </ul>
+ </div>
+</div>
+
<%= link_to 'Edit', edit_user_path(@user) %> |
-<%= link_to 'Back', users_path %>
+<%= link_to 'All Users', users_path %>
diff --git a/blue.yaml b/blue.yaml
new file mode 100644
index 0000000..720fa14
--- /dev/null
+++ b/blue.yaml
@@ -0,0 +1,200 @@
+---
+M9Fumjaa:
+ level: 16
+ goldEarned: 11160
+ numDeaths: 6
+ minionsKilled: 80
+ championsKilled: 4
+ goldSpent: 10405
+ totalDamageDealt: 86661
+ totalDamageTaken: 18253
+ team: 100
+ win: true
+ neutralMinionsKilled: 8
+ largestMultiKill: 1
+ physicalDamageDealtPlayer: 52332
+ magicDamageDealtPlayer: 34328
+ physicalDamageTaken: 9855
+ magicDamageTaken: 8323
+ largestCriticalStrike: 611
+ timePlayed: 2437
+ totalHeal: 467
+ totalUnitsHealed: 1
+ assists: 8
+ item0: 3087
+ item1: 3254
+ item2: 3031
+ item3: 3086
+ item4: 1018
+ item6: 3341
+ sightWardsBought: 3
+ visionWardsBought: 2
+ magicDamageDealtToChampions: 9814
+ physicalDamageDealtToChampions: 6069
+ totalDamageDealtToChampions: 15883
+ trueDamageTaken: 75
+ wardKilled: 5
+ wardPlaced: 5
+ neutralMinionsKilledEnemyJungle: 1
+ neutralMinionsKilledYourJungle: 7
+ totalTimeCrowdControlDealt: 593
+spikevsnaruto:
+ level: 17
+ goldEarned: 11608
+ numDeaths: 7
+ turretsKilled: 3
+ minionsKilled: 133
+ championsKilled: 3
+ goldSpent: 11275
+ totalDamageDealt: 97547
+ totalDamageTaken: 34598
+ doubleKills: 1
+ killingSprees: 1
+ largestKillingSpree: 3
+ team: 100
+ win: true
+ neutralMinionsKilled: 11
+ largestMultiKill: 2
+ physicalDamageDealtPlayer: 58212
+ magicDamageDealtPlayer: 38603
+ physicalDamageTaken: 24930
+ magicDamageTaken: 8900
+ timePlayed: 2437
+ totalHeal: 198
+ totalUnitsHealed: 1
+ assists: 6
+ item0: 3083
+ item1: 3074
+ item2: 3143
+ item4: 1001
+ item6: 3340
+ magicDamageDealtToChampions: 6017
+ physicalDamageDealtToChampions: 5486
+ totalDamageDealtToChampions: 12235
+ trueDamageDealtPlayer: 732
+ trueDamageDealtToChampions: 732
+ trueDamageTaken: 768
+ wardPlaced: 8
+ neutralMinionsKilledEnemyJungle: 5
+ neutralMinionsKilledYourJungle: 6
+ totalTimeCrowdControlDealt: 1128
+GoogleMaSkills:
+ level: 16
+ goldEarned: 12384
+ numDeaths: 7
+ barracksKilled: 1
+ turretsKilled: 2
+ minionsKilled: 107
+ championsKilled: 4
+ goldSpent: 12065
+ totalDamageDealt: 134161
+ totalDamageTaken: 34963
+ killingSprees: 2
+ largestKillingSpree: 2
+ team: 100
+ win: true
+ neutralMinionsKilled: 40
+ largestMultiKill: 1
+ physicalDamageDealtPlayer: 122998
+ magicDamageDealtPlayer: 726
+ physicalDamageTaken: 28243
+ magicDamageTaken: 6299
+ largestCriticalStrike: 582
+ timePlayed: 2437
+ totalHeal: 6236
+ totalUnitsHealed: 1
+ assists: 8
+ item0: 3035
+ item1: 3046
+ item2: 1037
+ item3: 3072
+ item4: 3006
+ item5: 1018
+ item6: 3340
+ physicalDamageDealtToChampions: 7310
+ totalDamageDealtToChampions: 7420
+ trueDamageDealtPlayer: 10436
+ trueDamageDealtToChampions: 110
+ trueDamageTaken: 420
+ wardPlaced: 9
+ neutralMinionsKilledEnemyJungle: 15
+ neutralMinionsKilledYourJungle: 25
+ totalTimeCrowdControlDealt: 236
+james chamberlan:
+ level: 18
+ goldEarned: 17239
+ numDeaths: 9
+ barracksKilled: 1
+ turretsKilled: 2
+ minionsKilled: 179
+ championsKilled: 14
+ goldSpent: 14495
+ totalDamageDealt: 212794
+ totalDamageTaken: 31349
+ doubleKills: 3
+ tripleKills: 1
+ killingSprees: 3
+ largestKillingSpree: 5
+ team: 100
+ win: true
+ neutralMinionsKilled: 24
+ largestMultiKill: 3
+ physicalDamageDealtPlayer: 179480
+ magicDamageDealtPlayer: 31260
+ physicalDamageTaken: 17890
+ magicDamageTaken: 12640
+ largestCriticalStrike: 898
+ timePlayed: 2437
+ totalHeal: 286
+ totalUnitsHealed: 1
+ assists: 8
+ item0: 3072
+ item1: 3046
+ item2: 3031
+ item3: 3250
+ item4: 3071
+ item6: 3340
+ sightWardsBought: 1
+ magicDamageDealtToChampions: 8376
+ physicalDamageDealtToChampions: 28456
+ totalDamageDealtToChampions: 37504
+ trueDamageDealtPlayer: 2054
+ trueDamageDealtToChampions: 672
+ trueDamageTaken: 819
+ wardKilled: 1
+ wardPlaced: 5
+ neutralMinionsKilledEnemyJungle: 4
+ neutralMinionsKilledYourJungle: 20
+ totalTimeCrowdControlDealt: 777
+Kaceytron:
+ level: 15
+ goldEarned: 9776
+ numDeaths: 16
+ barracksKilled: 1
+ minionsKilled: 118
+ goldSpent: 8245
+ totalDamageDealt: 104719
+ totalDamageTaken: 25219
+ team: 100
+ win: true
+ physicalDamageDealtPlayer: 10231
+ magicDamageDealtPlayer: 94127
+ physicalDamageTaken: 16694
+ magicDamageTaken: 8368
+ timePlayed: 2437
+ totalHeal: 163
+ totalUnitsHealed: 1
+ assists: 5
+ item0: 3174
+ item1: 3135
+ item2: 1026
+ item3: 3108
+ item4: 1058
+ item6: 3340
+ magicDamageDealtToChampions: 13741
+ physicalDamageDealtToChampions: 1933
+ totalDamageDealtToChampions: 16034
+ trueDamageDealtPlayer: 360
+ trueDamageDealtToChampions: 360
+ trueDamageTaken: 156
+ totalTimeCrowdControlDealt: 372
diff --git a/config/application.example.yml b/config/application.example.yml
new file mode 100644
index 0000000..a98b40e
--- /dev/null
+++ b/config/application.example.yml
@@ -0,0 +1,3 @@
+SECRET_TOKEN: 'cc884af613d0dd093f1d6c9153abac1200c5a0db923613245b80c5c3f5e9c9f9ba51712b702f2d494a22ddea8ab40601b38a41eb39eec97b50a7a2e37748b1bc'
+RIOT_API_KEY: 'ad539f86-22fd-474d-9279-79a7a296ac38'
+RIOT_API_REGION: 'na'
diff --git a/config/application.rb b/config/application.rb
index 658e0aa..13bab0b 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -12,6 +12,9 @@ module Leaguer
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
+ config.autoload_paths += ["#{Rails.root}/lib"]
+ config.watchable_dirs["#{Rails.root}/lib"] = [:rb]
+
I18n.enforce_available_locales = true
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
diff --git a/config/initializers/mailboxer.rb b/config/initializers/mailboxer.rb
index 8876f80..b529481 100644
--- a/config/initializers/mailboxer.rb
+++ b/config/initializers/mailboxer.rb
@@ -1,7 +1,7 @@
Mailboxer.setup do |config|
#Configures if you applications uses or no the email sending for Notifications and Messages
- config.uses_emails = true
+ config.uses_emails = false
#Configures the default from for the email sent for Messages and Notifications of Mailboxer
config.default_from = "no-reply@mailboxer.com"
diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb
index 604d43d..fbab4b9 100644
--- a/config/initializers/secret_token.rb
+++ b/config/initializers/secret_token.rb
@@ -9,4 +9,8 @@
# Make sure your secret_key_base is kept private
# if you're sharing your code publicly.
-Leaguer::Application.config.secret_key_base = 'cc884af613d0dd093f1d6c9153abac1200c5a0db923613245b80c5c3f5e9c9f9ba51712b702f2d494a22ddea8ab40601b38a41eb39eec97b50a7a2e37748b1bc'
+Leaguer::Application.config.secret_key_base = if Rails.env.development? or Rails.env.test?
+ ('x' * 30) # meets minimum requirement of 30 chars long
+else
+ ENV['SECRET_TOKEN']
+end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 0653957..9b7f013 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -21,3 +21,11 @@
en:
hello: "Hello world"
+
+ simple_captcha:
+ placeholder: "Enter the image value"
+ label: "Enter the code in the box:"
+
+ message:
+ default: "Secret Code did not match with the Image"
+ user: "The secret Image and code were different"
diff --git a/config/routes.rb b/config/routes.rb
index 262a156..4094479 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,24 +1,46 @@
Leaguer::Application.routes.draw do
- resources :brackets
- resources :sessions
+ resources :sessions, only: [:new, :create, :destroy]
resources :users
resources :games
- resources :tournaments
-
resources :pms
resources :alerts
resources :teams
- resources :matches
+ resources :tournaments do
+ resources :matches, only: [:index, :show, :update]
+ resources :brackets, only: [:create, :index, :show, :edit, :update, :destroy]
+ end
+
+ resource :server, only: [:show, :edit, :update]
+
+ root to: 'main#homepage'
+
+ get '/search', to: 'search#go'
- resources :servers
+end
+
+Leaguer::Application.routes.named_routes.module.module_eval do
+ def match_path(match, options={})
+ tournament_match_path(match.tournament_stage.tournament, match, options)
+ end
+ def match_url(match, options={})
+ tournament_match_url(match.tournament_stage.tournament, match, options)
+ end
+ def bracket_path(bracket, options={})
+ tournament_bracket_path(bracket.tournament, bracket, options)
+ end
+ def bracket_url(bracket, options={})
+ tournament_bracket_url(bracket.tournament, bracket, options)
+ end
+end
+if false
# The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes".
diff --git a/db.sh b/db.sh
new file mode 100755
index 0000000..9d86834
--- /dev/null
+++ b/db.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+bundle exec rake db:drop && bundle exec rake db:migrate && bundle exec rake db:seed
diff --git a/db/seeds.rb b/db/seeds.rb
index 4edb1e8..cccf3e5 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -3,5 +3,199 @@
#
# Examples:
#
-# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
-# Mayor.create(name: 'Emanuel', city: cities.first)
+# cities = City.create!([{ name: 'Chicago' }, { name: 'Copenhagen' }])
+# Mayor.create!(name: 'Emanuel', city: cities.first)
+#
+p = User.permission_bits
+Server.create!(default_user_permissions: p[:join_tournament] | p[:create_pm] | p[:edit_pm] | p[:create_bracket])
+
+league = Game.create!(name: "League of Legends", min_players_per_team: 5, max_players_per_team: 5, min_teams_per_match: 2, max_teams_per_match: 2)
+league.settings.create!(display_order: 1, name: "map" , description: "Select a map to play on.", vartype: GameSetting::types[:pick_one_dropdown], type_opt: "summoners_rift,twisted_treeline,crystal_scar,haunted_abyss", default: "summoners_rift")
+league.settings.create!(display_order: 2, name: "pick_type", description: "Select a pick type." , vartype: GameSetting::types[:pick_one_dropdown], type_opt: "blind_pick,draft" , default: "draft")
+
+chess = Game.create!(name: "Chess", min_players_per_team: 1, max_players_per_team: 1, min_teams_per_match: 2, max_teams_per_match: 2)
+chess.settings.create!(display_order: 1, name: "time_control", description: "Enter a value for Time Control (ie. 5-5, 30, 6hr, or None)", vartype: GameSetting::types[:text_short], default: "")
+
+hearthstone = Game.create!(name: "Hearthstone", min_players_per_team: 1, max_players_per_team: 1, min_teams_per_match: 2, max_teams_per_match: 2)
+hearthstone.settings.create!(display_order: 1, name: "deck_name", description: "Enter a name for your deck, be descriptive.", vartype: GameSetting::types[:text_long], default: "")
+
+rockpaperscissors = Game.create!(name: "Rock, Paper, Scissors", min_players_per_team: 1, max_players_per_team: 3, min_teams_per_match: 2, max_teams_per_match: 2)
+rockpaperscissors.settings.create!(display_order: 4, name: "lizard_spock_allowed", description: "Will you allow Lizard and Spock?" , vartype: GameSetting::types[:true_false] , default: false)
+rockpaperscissors.settings.create!(display_order: 5, name: "favorite_object" , description: "What is your favorite object in RPS?", vartype: GameSetting::types[:pick_one_radio], type_opt: "rock,paper,scissors", default: "rock")
+rockpaperscissors.settings.create!(display_order: 6, name: "check_boxes" , description: "Example boxes" , vartype: GameSetting::types[:pick_several] , type_opt: "i_do_not_know,there_is_now_spoon,wow,because_electricity,wat?", default: "wow,wat?")
+
+if Rails.env.development?
+ # User 1, the ADMIN
+ admin = User.create!(name: "Administrator", user_name: "admin", email: "root@localhost.lan", password: "password", permissions: 0xFFFFFFFF)
+
+ # John Doe's for testing
+ User.create!(name: "John 0", password: "password", email: "john0@gmail.com", user_name: "johndoe0")
+ User.create!(name: "John 1", password: "password", email: "john1@gmail.com", user_name: "johndoe1")
+ User.create!(name: "John 2", password: "password", email: "john2@gmail.com", user_name: "johndoe2")
+ User.create!(name: "John 3", password: "password", email: "john3@gmail.com", user_name: "johndoe3")
+ User.create!(name: "John 4", password: "password", email: "john4@gmail.com", user_name: "johndoe4")
+ User.create!(name: "John 5", password: "password", email: "john5@gmail.com", user_name: "johndoe5")
+ User.create!(name: "John 6", password: "password", email: "john6@gmail.com", user_name: "johndoe6")
+ User.create!(name: "John 7", password: "password", email: "john7@gmail.com", user_name: "johndoe7")
+ User.create!(name: "John 8", password: "password", email: "john8@gmail.com", user_name: "johndoe8")
+ User.create!(name: "John 9", password: "password", email: "john9@gmail.com", user_name: "johndoe9")
+
+ # Users for mocked Riot API calls
+ players_for_league = []
+ players_for_league.push(User.create!(name: "Sytrie" , password: "password", email: "Sytrie@gmail.com" , user_name: "Sytrie" ))
+ players_for_league.push(User.create!(name: "Derpanator115" , password: "password", email: "Derpanator115@gmail.com" , user_name: "Derpanator115" ))
+ players_for_league.push(User.create!(name: "Wlknexe56" , password: "password", email: "Wlknexe56@gmail.com" , user_name: "Wlknexe56" ))
+ players_for_league.push(User.create!(name: "DVisionzz" , password: "password", email: "DVisionzz@gmail.com" , user_name: "DVisionzz" ))
+ players_for_league.push(User.create!(name: "HYP3RTONIC" , password: "password", email: "HYP3RTONIC@gmail.com" , user_name: "HYP3RTONIC" ))
+ players_for_league.push(User.create!(name: "M9Fumjaa" , password: "password", email: "M9Fumjaa@gmail.com" , user_name: "M9Fumjaa" ))
+ players_for_league.push(User.create!(name: "spikevsnaruto" , password: "password", email: "spikevsnaruto@gmail.com" , user_name: "spikevsnaruto" ))
+ players_for_league.push(User.create!(name: "GoogleMaSkills" , password: "password", email: "GoogleMaSkills@gmail.com" , user_name: "GoogleMaSkills" ))
+ players_for_league.push(User.create!(name: "james chamberlan", password: "password", email: "jameschamberlan@gmail.com", user_name: "james chamberlan"))
+ players_for_league.push(User.create!(name: "Kaceytron" , password: "password", email: "Kaceytron@gmail.com" , user_name: "Kaceytron" ))
+
+ # Semi-real users
+ guntas = User.create!(name: "Guntas Grewal" , password: "password", email: "guntasgrewal@gmail.com" , user_name: "guntasgrewal")
+ luke = User.create!(name: "Luke Shumaker" , password: "password", email: "lukeshu@emacs4lyfe.com" , user_name: "lukeshu" )
+ tomer = User.create!(name: "Tomer Kimia" , password: "password", email: "tomer@2majors4lyfe.com" , user_name: "tkimia" )
+ josh = User.create!(name: "Josh Huser" , password: "password", email: "jhuser@iownabusiness.net" , user_name: "WinterWorks" )
+ dunsmore = User.create!(name: "Professor Dunsmore", password: "password", email: "bxd@purdue.edu" , user_name: "Dumbledore" )
+ marco = User.create!(name: "Marco Polo" , password: "password", email: "marco@ta4lyfe.com" , user_name: "iCoordinate" )
+ jordan = User.create!(name: "Geoffrey Webb" , password: "password", email: "imnotjoffreybarathian@gameofthrones.com", user_name: "GTBPhoenix" )
+ obama = User.create!(name: "Obama" , password: "password", email: "obama@whitehouse.gov" , user_name: "Obama" )
+
+ davis = User.create!(name: "Davis Webb" , password: "password", email: "davislwebb@gmail.com" , user_name: "TeslasMind" )
+ foy = User.create!(name: "Nathaniel Foy" , password: "password", email: "nfoy@purdue.edu" , user_name: "NalfeinX" )
+ andrew = User.create!(name: "Andrew Murrell" , password: "password", email: "murrel@murrel.gov" , user_name: "ImFromNasa" )
+ joey = User.create!(name: "Joseph Adams" , password: "password", email: "alpha142@fluttershyop.com" , user_name: "alpha142" )
+ panda = User.create!(name: "Panda" , password: "password", email: "panda@gmail.com" , user_name: "InspectorPanderp")
+ mesa = User.create!(name: "Mesataki" , password: "password", email: "mesataki@gmail.com" , user_name: "Mesataki" )
+ guntas_league = User.create!(name: "TolkiensButt" , password: "password", email: "TolkiensButt@gmail.com" , user_name: "TolkiensButt" )
+ lyra = User.create!(name: "Lyra Heartstings" , password: "password", email: "LyraHeartstings@gmail.com" , user_name: "Lyra Heartstings")
+ josh_league = User.create!(name: "Josh_league" , password: "password", email: "josh_league@gmail.com" , user_name: "Joshoowah" )
+ jeff = User.create!(name: "Jeff Linguinee" , password: "password", email: "jefflingueeneeeee@gmail.com" , user_name: "SenorJeffafa" )
+ sarah = User.create!(name: "Sarah Lawson" , password: "password", email: "sarah@gmail.com" , user_name: "LittlexSurah" )
+
+ # League of Legends tournament
+ league_tourn = Tournament.create!(
+ game: league,
+ name: "League of Legends Seed",
+ min_players_per_team: 5,
+ max_players_per_team: 5,
+ min_teams_per_match: 2,
+ max_teams_per_match: 2,
+ scoring_method: "winner_takes_all",
+ hosts: [admin])
+ league_tourn.stages.create!(scheduling_method: "round_robin" , seeding_method: "random_seeding")
+ players_for_league.each do |player|
+ league_tourn.join(player)
+ end
+
+ # Chess
+ chess_tourn = Tournament.create!(
+ game: chess,
+ name: "Chess Seed",
+ min_players_per_team: 1,
+ max_players_per_team: 1,
+ min_teams_per_match: 2,
+ max_teams_per_match: 2,
+ scoring_method: "winner_takes_all",
+ hosts: [davis])
+ chess_tourn.stages.create!(scheduling_method: "round_robin" , seeding_method: "random_seeding")
+ chess_tourn.join(davis)
+ chess_tourn.join(foy)
+
+ # Rock Paper Scissors
+ rps = Tournament.create!(
+ game: rockpaperscissors,
+ name: "Rock, Paper, Scissors Seed",
+ min_players_per_team: 1,
+ max_players_per_team: 3,
+ min_teams_per_match: 2,
+ max_teams_per_match: 2,
+ scoring_method: "winner_takes_all",
+ hosts: [davis])
+ rps.stages.create!(scheduling_method: "round_robin" , seeding_method: "random_seeding")
+ rps.join(davis)
+ rps.join(foy)
+ rps.join(guntas)
+
+ # Another League tournament
+ tourn5 = Tournament.create!(
+ game: league,
+ name: "5 Teams, 2 Teams Per Match",
+ min_players_per_team: 1,
+ max_players_per_team: 1,
+ min_teams_per_match: 2,
+ max_teams_per_match: 2,
+ scoring_method: "winner_takes_all",
+ hosts: [admin])
+ tourn5.stages.create!(scheduling_method: "elimination" , seeding_method: "random_seeding")
+ players_for_league.each do |player|
+ tourn5.join(player)
+ end
+
+ # Yet another League tournament
+ tourn6 = Tournament.create!(
+ game_id: 1,
+ name: "3 teams per match",
+ min_players_per_team: 1,
+ max_players_per_team: 1,
+ min_teams_per_match: 3,
+ max_teams_per_match: 3,
+ scoring_method: "winner_takes_all",
+ hosts: [admin])
+ tourn6.stages.create!(scheduling_method: "round_robin" , seeding_method: "random_seeding")
+ players_for_league.each do |player|
+ tourn6.join(player)
+ end
+ tourn6.join(davis)
+ tourn6.join(foy)
+ tourn6.join(guntas)
+ tourn6.join(luke)
+ tourn6.join(marco)
+ tourn6.join(jordan)
+ tourn6.join(obama)
+ tourn6.join(joey)
+
+ #Hearthstone tournament
+ hearth = Tournament.create!(
+ game: hearthstone,
+ name: "Hearthstone Seed",
+ min_teams_per_match: 1,
+ min_players_per_team: 1,
+ max_teams_per_match: 2,
+ max_players_per_team: 1,
+ scoring_method: "winner_takes_all",
+ hosts: [admin])
+ hearth.stages.create!(scheduling_method: "round_robin" , seeding_method: "random_seeding")
+ hearth.join(davis)
+ hearth.join(foy)
+
+ #THE REAL GAME WE ARE PLAYING AT 10
+ davis.remote_usernames.create!( game: league, value: {"name" => "TeslasMind" , "id" => 30533514} )
+ foy.remote_usernames.create!( game: league, value: {"name" => "NalfeinX" , "id" => 29538130} )
+ andrew.remote_usernames.create!( game: league, value: {"name" => "ImFromNasa" , "id" => 29782091} )
+ joey.remote_usernames.create!( game: league, value: {"name" => "Alpha142" , "id" => 29732514} )
+ sarah.remote_usernames.create!( game: league, value: {"name" => "LittlexSurah" , "id" => 30613787} )
+ mesa.remote_usernames.create!( game: league, value: {"name" => "Mesataki" , "id" => 37259275} )
+ panda.remote_usernames.create!( game: league, value: {"name" => "NalfeinX" , "id" => 47953989} )
+ jordan.remote_usernames.create!( game: league, value: {"name" => "GTBPhoenix" , "id" => 29812020} )
+ josh_league.remote_usernames.create!(game: league, value: {"name" => "Joshoowah" , "id" => 26083333} )
+ jeff.remote_usernames.create!( game: league, value: {"name" => "SenorJeffafa" , "id" => 32612067} )
+ lyra.remote_usernames.create!( game: league, value: {"name" => "Lyra Heartstings", "id" => 32240762} )
+ g = [davis, joey, panda, mesa, josh_league, jordan, jeff, sarah, foy, andrew]
+
+ custom = Tournament.create!(
+ game: league,
+ name: "Real League Game",
+ min_players_per_team: 5,
+ max_players_per_team: 5,
+ min_teams_per_match: 2,
+ max_teams_per_match: 2,
+ scoring_method: "winner_takes_all",
+ hosts: [admin])
+ custom.stages.create!(scheduling_method: "round_robin" , seeding_method: "early_bird_seeding")
+ g.each do |player|
+ custom.join(player)
+ end
+end
diff --git a/doc/Sprint1-Retrospective.md b/doc/Sprint1-Retrospective.md
new file mode 100644
index 0000000..7bffde7
--- /dev/null
+++ b/doc/Sprint1-Retrospective.md
@@ -0,0 +1,220 @@
+---
+title: "Team 6 - Project Leaguer: Sprint 1 Retrospective"
+author: [ Nathaniel Foy, Guntas Grewal, Tomer Kimia, Andrew Murrell, Luke Shumaker, Davis Webb ]
+---
+
+# User Stories
+
+1) As an administrator, I would like to install and boot my own server.
+ - Alternately: As a developer, I would like a demo/testing server,
+ with a basic Rails setup.
+2) As a host/player, I would like to register and have an account.
+ - For this task, we will be creating the user registration and log
+ in capabilities for Leaguer.
+3) As a host, I would like to start a tournament.
+ - For this task, we will be creating a base tournament system for a
+ host to run.
+4) As a host/player, I would like to enter scores for players.
+ - For sprint own, the scores will be entered by hand.
+5) As an administrator, I want to specify how users become hosts.
+6) As a user I would like to see the progress of the tournament in my
+ browser.
+7) As a user, I would like a presentable homepage.
+ - For this task, we will be creating a Leaguer homepage and ensure that it
+ is pleasing to the eye and easy to navigate.
+
+# Tasks
+
+The "size" is using the modified Fibonacci scale. A '1' is expected
+to take less than an hour. A '3' is expected to take 3-6 hours. A
+'5' should take the better part of a day or two. An 8 should take
+several days.
+
++---------------------------------------------------------+------+--------+----+
+| Tasks Implemented and Working | Size | Person | US |
++=========================================================+======+========+====+
+| [Learn Rails, set up Scaffolding for all Models, Views, | 8 | All | 1 |
+| Controllers](#learn-rails) | | | |
++---------------------------------------------------------+------+--------+----+
+| [Deploy rails on Luke's server](#deploy-rails) | 3 | Luke | 1 |
++---------------------------------------------------------+------+--------+----+
+| [Create log-in system back-end (verification, cookies, | 5 | Davis | 2 |
+| and redirection)](#login-backend) | | | |
++---------------------------------------------------------+------+--------+----+
+| [Create log-in system UI](#login-ui) | 2 | Tomer | 2 |
++---------------------------------------------------------+------+--------+----+
+| [Create Tournament Settings Page](#tourney-settings) | 3 | Guntas | 3 |
++---------------------------------------------------------+------+--------+----+
+| [Implement Tournament Registration and Tournament | 2 | Andrew | 3 |
+| Controller](#tourney-registration) | | | |
++---------------------------------------------------------+------+--------+----+
+| [Implement match controller](#match-controller) | 3 | Dav+And| 4 |
++---------------------------------------------------------+------+--------+----+
+| [Implement permissions system over the users | 3 | Luke | 5 |
+| system](#permissions) | | | |
++---------------------------------------------------------+------+--------+----+
+| [Create View Tournament Page](#tourney-view) | 5 | All | 6 |
++---------------------------------------------------------+------+--------+----+
+| [Create Presentable Homepage](#homepage) | 5 | Guntas | 7 |
++---------------------------------------------------------+------+--------+----+
+
+
++---------------------------------------------------------+------+--------+----+
+| Tasks Implemented and Not Working Well | Size | Person | US |
++=========================================================+======+========+====+
+| [Design and implement match score models](#match-score) | 3 | Foy | 4 |
++---------------------------------------------------------+------+--------+----+
+| [Create Admin-level Server Management Page](#srv-man) | 2 | Luke | 5 |
++---------------------------------------------------------+------+--------+----+
+
+
++---------------------------------------------------------+------+--------+----+
+| Tasks Not Implemented | Size | Person | US |
++=========================================================+======+========+====+
+| [Design/Code Scoring/Pairing Algorithms and | 5 | Foy | 3 |
+| Procedures](#score-algo) | | | |
++---------------------------------------------------------+------+--------+----+
+| [Observe Foy Design/Code Scoring/Pairing | 2 | Dav+Foy| 3 |
+| Algorithms](#score-algo) | | | |
++---------------------------------------------------------+------+--------+----+
+| [Create a Player-level Data Entry Page/Method for | 3 | Tomer | 4 |
+| Results](#data-entry) | | | |
++---------------------------------------------------------+------+--------+----+
+
+# Implemented and working
+
+## Learn Rails {#learn-rails}
+
+Learning Rails has been a growing experience for the majority of the
+team. Some of us coming from no significant experience to being able
+to put together a relatively functional product in only three weeks
+has been an impressive journey.
+
+## Deploy Rails {#deploy-rails}
+
+The entire team became familiar with deploying Rails in our rather
+diverse working environments and successfully deployed a server
+instance located at demo.projectleaguer.net as well as on our local boxes.
+
+## Login (back-end) {#login-backend}
+
+Our login back-end successfully logs users in and our and can handle
+user registrations and first-come-first-serve uniqueness validation.
+
+## Login (UI) {#login-ui}
+
+Our login user interface successfully differentiates between logged in
+and logged out users as well as between users of different
+designations (although for the demo some of the hooks were not in
+place, this has been fixed).
+
+## Tournament settings {#tourney-settings}
+
+Tournament settings were implemented at a basic level, instituting those
+items which are similar to all tournaments, regardless of type, orginating
+from the game model.
+
+## Tournament registration {#tourney-registration}
+
+Tournament registration and the tournament contoller were completed which
+allowed users to join and participate in basic tournaments of several types.
+The tournament controller handled a variety of tournament related tasks,
+including creating and updating tournaments and validating tournament related
+operations.
+
+## Match controller {#match-controller}
+
+The Match Controller creates the separate matches for a specific tournament.
+When a tournament is started, it begins with an initial match that contains
+no players. Currently, a player must join a match by entering the specific
+tournament (by clicking the 'show' button on the tournament),
+then they must enter the match (again by clicking the 'show' button but this
+time on the match they desire to participate in) and then finally clicking
+the 'join' button. This updates the match with the user as a participant in
+the matc and then finally clicking the 'join' button. This updates the match
+with the user as a participant in the match. A match can also be destroyed
+by clicking the 'delete' button on the no longer desired match on the page.
+
+## Permissions system {#permissions}
+
+The permissions system is implemented, easy to use, and works well.
+In some places, it appears to be broken (overly-permisive), but this
+is because the relevant page doesn't hook into the permission system.
+This needs to be fixed with unit tests.
+
+## Tournament view {#tourney-view}
+The view page for tournaments contains a table that lists all on going
+tournaments for all types of games. It also lists other game attribute like
+Players per team, Teams per match, whether or not teams were randomized
+and also has links to Show, Edit, Destroy, Join a particular tournament.
+A link to create a tournament is also provided. Each of the links correspond
+to view pages which are html.erb pages that provide styles and functionality
+of each of the tournament setting features. Show, Edit, Destroy, all have
+views of their own to perform each of the above actions.
+
+## Homepage {#homepage}
+The homepage is mainly controlled by the views that are associated with each
+model and controller. The main view for the homepage is the one found in the
+layouts called application.html.erb, this view is responsible for display of
+all the headings, navigation bars, forms and containers. This view page
+contains mostly links to other view pages and yields whatever the other view
+pages have to offer. The Homepage redirects to Login, Signup, See Ongoing
+Tournaments and shows the view for those models.
+
+
+# Implemented but not working well
+
+## Match score models {#match-score}
+
+This only functioned properly for noting which team would win a match. We want
+more information to be included, such as individual player scores. We also
+only had it working where the tournament host would decide who won.
+
+## Server management {#srv-man}
+
+The server management software interface is implemented, and working
+fine. The other modules use it. However, what we didn't implement is
+an actual *page* to edit these settings. We had this task in the
+iteration because other items depended on it. Though we did not
+implement the full story, we implemented the core reason that we
+wanted it.
+
+# Not implemented
+
+## Scoring Algorithms {#score-algo}
+
+Scoring algorithms was not implemented because we did not have time for
+implementing player statistics in the first sprint. There were some
+preliminary approaches, but the task lost priority and was abandoned.
+
+## Data entry {#data-entry}
+
+It was decided to not be a priority for sprint one due to time constraints.
+Also, we want to implement data entry for League of Legends through
+Riot Games (TM)'s API for grabbing match data.
+
+# How to improve
+
+Peer reviews and testing were our biggest pitfalls.
+
+
+1. All testing was just manual, in-browser testing, rather than unit
+ tests. We really need write unit tests this iteration, as we had
+ breakages where we said "this is exactly why we need unit
+ testing." However, that happened late enough in the iteration that
+ we didn't have time to do anything about it.
+
+2. That leads us into time management. Our commit activity plotted
+ against time has humps each weak, each growing a little. That is,
+ we started slow, and ended with a lot of work. This wasn't exactly
+ poor planing, but we had a poor idea of how much time things would
+ take. We plan to fix this by front-loading this iteration instead
+ of back-loading it.
+
+3. We had the approach of "show everyone everything" with peer
+ reviews, as we anticipated that this would be nescessary for
+ everyone learning Rails. However, in effect it meant that
+ sometimes information was spread very thin, or because things were
+ being done "in the open", we didn't ever explicitly review them.
+ We plan on fixing this next iteration by committing to do very
+ specific peer reviews with just a couple members of the team.
diff --git a/doc/Sprint2-Retrospective.md b/doc/Sprint2-Retrospective.md
new file mode 100644
index 0000000..9a98ec5
--- /dev/null
+++ b/doc/Sprint2-Retrospective.md
@@ -0,0 +1,215 @@
+---
+title: "Team 6 - Project Leaguer: Sprint 2 Retrospective"
+author: [ Nathaniel Foy, Guntas Grewal, Tomer Kimia, Andrew Murrell, Luke Shumaker, Davis Webb ]
+---
+
+# Tasks
+
+The "size" is using the modified Fibonacci scale. A '1' is expected
+to take less than an hour. A '3' is expected to take 3-6 hours. A
+'5' should take the better part of a day or two. An 8 should take
+several days.
+
++---------------------------------------------------------+------+------------+----+
+| Tasks Implemented and Working | Size | Person\* | US |
++=========================================================+======+============+====+
+| [Implement Anti-spam measures](#anti-spam) | 2 | Davis | 2 |
++---------------------------------------------------------+------+------------+----+
+| [Implement Teammate Rating System (peer review view)] | 5 | Guntas | 3 |
+| (#peer-review) | | | |
++---------------------------------------------------------+------+------------+----+
+| [Design/Code Scoring/Pairing Algorithms and Procedures] | 5 | D+F+A | 3 |
+| (#pair-alg) | | | |
++---------------------------------------------------------+------+------------+----+
+| [Implement game-type specific and tournament | 8 | L+A+G | 4 |
+| specific settings and preferences] (#setting-and-pref) | | | |
++---------------------------------------------------------+------+------------+----+
+| [Retrieve data from Riot Games (TM) API ](#riot-api) | 3 | Foy | 5 |
++---------------------------------------------------------+------+------------+----+
+| [Parse Riot data and attach to scoring subsystem] | 5 | Davis | 5 |
+| (#parse-riot) | | | |
++---------------------------------------------------------+------+------------+----+
+| [Teach Andrew and Tomer AJAX ](#teach-ajax) | 2 | Luke | 6 |
++---------------------------------------------------------+------+------------+----+
+| [Make pages auto-update with AJAX](#ajax) | 5 | T+A | 6 |
++---------------------------------------------------------+------+------------+----+
+| [Setting up a Tournament View for matches and tree] | 5 | Tomer | 7 |
+| (#match-gui) | | | |
++---------------------------------------------------------+------+------------+----+
+| [Increase Usability](#usability) | 3 | All-L | 8 |
++---------------------------------------------------------+------+------------+----+
+| [Develop comprehensive data storage for s&p&other] | 5 | L+A | 9 |
+| (#data-storage) | | | |
++---------------------------------------------------------+------+------------+----+
+| [Create Player Profile Pages](#profile) | 2 | Tomer | 10 |
++---------------------------------------------------------+------+------------+----+
+| [Gravatar Integration](#gravatar) | 2 | Foy | 10 |
++---------------------------------------------------------+------+------------+----+
+
+
+
+
++---------------------------------------------------------+------+------------+----+
+| Tasks Implemented and Not Working Well | Size | Person\* | US |
++=========================================================+======+============+====+
+| [Not Applicable](#all-or-nothing) | 0 | --- | 0 |
++---------------------------------------------------------+------+------------+----+
+
+
+
+
++---------------------------------------------------------+------+------------+----+
+| Tasks Not Implemented | Size | Person\* | US |
++=========================================================+======+============+====+
+| [Email Verification Option](#email-varify) | 5 | Luke | 2 |
++---------------------------------------------------------+------+------------+----+
+| [Project Leaguer Logo](#logo) | spike| D+G | 8 |
++---------------------------------------------------------+------+------------+----+
+| [Define Specific Unit Tests for Security] | 3 | All | 1 |
+| (#security-test) | | | |
++---------------------------------------------------------+------+------------+----+
+
+
+
+
+# Implemented and working
+
+## Implement Anti-spam measures {#anti-spam}
+
+To handle potential spam problems, Project Leaguer has implemented Simple Captcha
+on the user sign up page. Users must enter the correct code corresponding with
+Simple Captcha's generated image when registering. Usernames must also be unique.
+E-mail verification has been pushed to Sprint 3.
+
+## Implement Teammate Rating System (peer review view) {#peer-review}
+
+This sprint covered both the database framework and actual implementation of the
+peer review rating system. Peer review was accomplished with both server-side
+processing and client-side manipulation of the DOM via a floating tactile dragable
+info-box interface.
+
+## Design/Code Scoring/Pairing Algorithms and Procedures {#pair-alg}
+
+Several scoring algorithms were considered for demonstration purposes for this
+sprint and eventually a modified fibonachi peer review system was chosen as the
+most fair system. This was the only scoring algorithm implemented in this sprint.
+A single-elimination pairing algorithm was chosen for similar reasons (as well
+as for simplicity in SVG generation).
+
+## Implement game-type specific and tournament specific settings and preferences
+ {#setting-and-pref}
+
+The input for settings and preferences for creating tournaments are displayed
+dynamically in both the substance of the content and form in which it is displayed.
+
+## Retrieve data from Riot Games (TM) API {#riot-api}
+
+Grabbing League of Legends user and match data from Riot's servers has been
+implemented using their newly available API. A developer key is necessary in
+order to retrieve data from their servers. We currently are using Davis's to do
+so. Information is grabbed with HTTParty.get and the correct url. A hash of
+information is stored this way. Grabbing information for a user requires the
+user's League of Legends summoner's name or summoner id. Our current developer
+key is limited to utilizing 10 pulls per 10 seconds.
+
+## Parse Riot data and attach to scoring subsystem {#parse-riot}
+
+We successfully parse the data we recieve from the Riot servers. The information
+is stored in a JSON hash which we separate based on the information we want (like
+kills, deaths, etc). One issue with our current pull method is that it can exceed
+the pull limit that is on our current development key. To fix this, we are planning
+on implementing a remote user id to link users Leaguer information to their Riot
+information.
+
+## Teach Andrew and Tomer AJAX {#teach-ajax}
+
+Luke instructed Tomer on his AJAX tasks, but most of Andrew's were deferred to
+sprint 3 and he focused his efforts elsewhere.
+
+## Make pages auto-update with AJAX {#ajax}
+
+AJAX was used in tournament and match views to update the tournament progress bar
+and manage input options for tournament flow but still needs to be implemented
+across the website in other areas.
+
+## Setting up a Tournament View for matches and tree {#match-gui}
+
+A new system was set-up so that matches are created from the trunk (final match) to
+the most out matches, and teams are inserted into matches starting at the leaves and
+and filling up the trunk. Any number of teams is now supported. A lot of log-based
+math was used to write the rails-generated SVG, and a lot of arithmetic was done to
+calculate the relative proportions.
+
+## Increase Usability {#usability}
+
+Project Leaguer has many new features that have increased usability. AJAX
+integration, tournament visuals (ready bar, match trees), Gravatar images,
+and Riot API integration all contribute towards an easier and more automatic
+web interface available for our users to utilize.
+
+## Develop comprehensive data storage for s&p&other{#data-storage}
+
+Settings and Preferences (those options specific to tournaments of a game type
+or a specific tournament, respectively) are handled through a single
+TournamentPreference SQL (ActiveRecord) interface.
+
+## Create Player Profile Pages {#profile}
+
+Player Profile Pages successfully list important and useful user information.
+Player username, e-mail, relationship status, and recent tournament information
+are all listed on a user's profile page. Gravatar images are also shown here.
+Users can also edit their pages.
+
+## Gravatar Integration {#gravatar}
+
+Gravatar images are fetched from the gravatar website. A user's e-mail is used to
+generate a hash key and that key is used to grab their gravatar image from a url.
+If their e-mail is not recognized by Gravatar, then we have a wide number of
+optionable default images to use. We currently use a mystery man default. It's
+also possible to utilize a number of other image options, such as sizing.
+
+
+
+# Implemented but not working well
+
+## Not Applicable {#all-or-nothing}
+
+Everything we implemented was implemented well, or else we didn't implement it.
+
+
+
+# Not implemented
+
+## Email Verification Option {#email-verify}
+
+This was not implemented for lack of time. Luke probably would have been able to
+implement it with his time constraints if he wasn't busy frequently assisting
+other members with various problems. In the end, the email verification was also
+simply a low priority.
+
+## Project Leaguer Logo {#logo}
+
+The Project Leaguer Logo was discussed before the sprint started. We decided we
+would follow up on any opportunities to explore creating a Leauger Logo, but this
+simply did not happen. We greatly want one, but it's still just a low priority
+extra feature.
+
+## Define Specific Unit Tests for Security {#security-test}
+
+Because of heavy "dog-food" style testing during the development process and
+fairly heavy rapid redesign, specific unit tests were not given a high priority
+for this sprint. The interdependency of components for tournament logic provided
+near-instant feedback when something was wrong.
+
+# How to improve
+
+1. We can better document our code with proper commentation and indentation. The
+team has run into issues where we've become confused with our own code and wasted
+time reviewing code.
+
+2. Our commits slowed to a halt the week before spring break. In this upcoming
+sprint we plan to take a stronger initiative and take a running start rather than
+a last lap dash.
+
+3. We can more carefully push and merge. We ran into a couple issues where team
+members broke each others work. These mistakes cost a lot of time to fix.
diff --git a/doc/Sprint2.md b/doc/Sprint2.md
new file mode 100644
index 0000000..a823600
--- /dev/null
+++ b/doc/Sprint2.md
@@ -0,0 +1,103 @@
+---
+title: "Team 6 - Project Leaguer: Sprint 2"
+author: [ Nathaniel Foy, Guntas Grewal, Tomer Kimia, Andrew Murrell, Luke Shumaker, Davis Webb ]
+---
+
+# User Stories
+
+1) As an admin, I would like hosts/players, to have only the options
+ their group entitles them to.
+
+2) As an admin, I would like anti-spam measures for registration.
+
+3) As a player I would like to review my peers and have our
+ scores reflect these reviews.
+
+4) As a host I would like to have both game-type specific settings and
+ tournament specific preferences available when creating a new
+ tournament.
+ - These settings and preferences were moved to sprint 2 from sprint 1
+ because we did not have the API functionality for any specific game yet.
+
+5) As a host/player/spectator I would like to have Riot Games League
+ of Legends API integration for match and player statistics and results for
+ League of Legends tournaments.
+
+6) As a host/player, I would like my pages to actively update without
+ refreshing my current page.
+ - For this task, we will implement an Active Status Update system with AJAX.
+
+7) As a host/player, I would like to see an interactive tournament lobby page
+ that displays tournament and match information.
+ - This will be accomplished with dynamic graphs, trees, and status bars.
+
+8) As a host/player, I would like the Leaguer application to be more intuitive
+ and easy to use.
+
+9) As a user, I would like past tournament and player information to be
+ persistent and search-able.
+ - A working search utility should be implemented that will find specific
+ players or tournaments and return their pages.
+
+10) As a user, I would like to see Player Profile pages.
+ - For this task, we will be creating profile pages for registered users that
+ have player-specific information such as tournament history and activity.
+
+# Tasks
+
+The "size" is using the modified Fibonacci scale. A '1' is expected
+to take less than an hour. A '3' is expected to take 3-6 hours. A
+'5' should take the better part of a day or two. An 8 should take
+several days.
+
++---------------------------------------------------------+------+------------+----+
+| Task Description | Size | Person\* | US |
++=========================================================+======+============+====+
+| Define Specific Unit Tests for Security | 3 | All | 1 |
++---------------------------------------------------------+------+------------+----+
+| Implement Anti-spam measures | 2 | Davis | 2 |
++---------------------------------------------------------+------+------------+----+
+| Email Verification Option | 5 | Luke | 2 |
++---------------------------------------------------------+------+------------+----+
+| Implement Teammate Rating System (peer review view) | 5 | Guntas | 3 |
++---------------------------------------------------------+------+------------+----+
+| Design/Code Scoring/Pairing Algorithms and Procedures | 5 | D+F+A | 3 |
++---------------------------------------------------------+------+------------+----+
+| Implement game-type specific and tournament | 8 | L+A+G | 4 |
+| specific settings and preferences | | | |
++---------------------------------------------------------+------+------------+----+
+| Retrieve data from Riot Games (TM) API | 3 | Foy | 5 |
++---------------------------------------------------------+------+------------+----+
+| Parse Riot data and attach to scoring subsystem | 5 | Davis | 5 |
++---------------------------------------------------------+------+------------+----+
+| Teach Andrew and Tomer AJAX | 2 | Luke | 6 |
++---------------------------------------------------------+------+------------+----+
+| Make pages auto-update with AJAX | 5 | T+A | 6 |
++---------------------------------------------------------+------+------------+----+
+| Setting up a Tournament View for matches and tree | 5 | Tomer | 7 |
++---------------------------------------------------------+------+------------+----+
+| Increase Usability | 3 | All-L | 8 |
++---------------------------------------------------------+------+------------+----+
+| Project Leaguer Logo | spike| D+G | 8 |
++---------------------------------------------------------+------+------------+----+
+| Develop comprehensive data storage for s&p&other | 5 | L+A | 9 |
++---------------------------------------------------------+------+------------+----+
+| Create Player Profile Pages | 2 | Tomer | 10 |
++---------------------------------------------------------+------+------------+----+
+| Gravitar Integration | 2 | Foy | 10 |
++---------------------------------------------------------+------+------------+----+
+| Test it | 1 | All-L | all|
++---------------------------------------------------------+------+------------+----+
+| Peer review | 1 | All | all|
++---------------------------------------------------------+------+------------+----+
+
+Total Size of Iteration: 55
+
+ D = Davis = 10
+ A = Andrew = 10
+ F = Nathaniel = 10
+ G = Guntas = 10
+ L = Luke = 11
+ T = Tomer = 10
+
+\* `+` means those members work together, `-` means exclude following members
diff --git a/doc/Sprint3.md b/doc/Sprint3.md
new file mode 100644
index 0000000..31f505e
--- /dev/null
+++ b/doc/Sprint3.md
@@ -0,0 +1,80 @@
+---
+title: "Team 6 - Project Leaguer: Sprint 3"
+author: [ Nathaniel Foy, Guntas Grewal, Tomer Kimia, Andrew Murrell, Luke Shumaker, Davis Webb ]
+---
+
+# User Stories
+
+1) As a user, I would like the web interface to look more professional and complete.
+
+2) As an admin, I would like to have an email verification system for a more secure
+ and spam free enviroment.
+
+3) As a user, I would like intelligent error handling (e.g. 404 and 403 redirection).
+
+4) As an admin, I would like to send alerts to users.
+
+5) As a user, I would like to be able to send private messages.
+
+6) As a user, I would like a working search utility.
+
+7) As a host or player, I would like customizable settings for peer evaluation and
+ scoring.
+
+8) As a host, I would like to have multiple tournament structures and types for
+ pairing and running tournaments. (e.g. Round Robin team pairings).
+
+9) As a host, I would like to have an interface for adding tournament-specific
+ preferences (e.g. Capture the Teemo).
+
+10) As a user, I would like to view and create brackets.
+
+11) As a user, I would like the Riot API to be asynchronously polled in the
+ background so League of Legends tournaments proceed automatically.
+
+12) As a player, I would like a way to enter my usernames for several different
+ remote games.
+
+
+#Tasks
+
+The "size" is using the modified Fibonacci scale. A '1' is expected
+to take less than an hour. A '3' is expected to take 3-6 hours. A
+'5' should take the better part of a day or two. An 8 should take
+several days.
+
++---------------------------------------------------------+------+------------+----+
+| Task Description | Size | Person | US |
++=========================================================+======+============+====+
+| Intelligent Error Handling (404 redirection) | 3 | Andrew | 3 |
++---------------------------------------------------------+------+------------+----+
+| Search | 5 | Tomer | 6 |
++---------------------------------------------------------+------+------------+----+
+| Remote Game UserNames | 3 | Davis | 12 |
++---------------------------------------------------------+------+------------+----+
+| Email verification | 8 | Luke | 2 |
++---------------------------------------------------------+------+------------+----+
+| Alternate Scoring and pairing methods | 5 | G, A, D | 7,8|
++---------------------------------------------------------+------+------------+----+
+| Tournament preference interace | 3 | Andrew | 9 |
++---------------------------------------------------------+------+------------+----+
+| More types of seeded settings | 2 | Andrew | 9 |
++---------------------------------------------------------+------+------------+----+
+| Asynchronous Riot Pulls | 5 | Nathaniel | 11 |
++---------------------------------------------------------+------+------------+----+
+| Map out brackets scaffolding | 5 | Tomer | 10 |
++---------------------------------------------------------+------+------------+----+
+| Create braket creation and submission gui | 3 | Tomer | 10 |
++---------------------------------------------------------+------+------------+----+
+| General Interface Cleanups | 2 | Tomer | 1 |
++---------------------------------------------------------+------+------------+----+
+| Make it look professional | 3 | All | 1 |
++---------------------------------------------------------+------+------------+----+
+| Expand Peer Evaluation | 3 | G, A, D | 7 |
++---------------------------------------------------------+------+------------+----+
+| Private Messages | 5 | N, L | 5 |
++---------------------------------------------------------+------+------------+----+
+| Alerts | 3 | Guntas | 4 |
++---------------------------------------------------------+------+------------+----+
+| Project Leaguer Logo | spike| G, D | 1 |
++---------------------------------------------------------+------+------------+----+
diff --git a/doc/sprint2Retro.md b/doc/sprint2Retro.md
new file mode 100644
index 0000000..1a40bac
--- /dev/null
+++ b/doc/sprint2Retro.md
@@ -0,0 +1,12 @@
+Riot API
+
+The Riot API allowed us to pull information from the Riot servers for League of Legends
+Tournaments. For sprint 2, we were able to pull the information, but not very efficiently.
+We ran into an issue where we were making too many pull requests for our Riot developer key
+which would gave us some issues.
+
+Anti-Spam
+
+For now, the only anti-spam protection that we have is a captcha at the user creation page.
+Our hopes is to implement an email verification system in order to help further protect
+Project Leaguer from spam.
diff --git a/lib/sampling/README.md b/lib/sampling/README.md
new file mode 100644
index 0000000..e4b3fbf
--- /dev/null
+++ b/lib/sampling/README.md
@@ -0,0 +1,50 @@
+Sampling interface
+==================
+
+Files in this directory should be _classes_ implementing the following
+interface:
+
+ - `self.works_with?(Game) => Boolean`
+
+ Returns whether or not this sampling method works with the
+ specified game.
+
+ - `self.can_get?(String setting_name) => Fixnum`
+
+ Returns whether or not this sampling method can get a specifed
+ statistic; 0 means 'false', positive integers mean 'true', where
+ higher numbers are higher priority.
+
+ - `self.uses_remote?() => Boolean`
+
+ Return whether or not this sampling method requires remote IDs for
+ users.
+
+ - `self.set_remote_name(User, Game, String)`
+
+ Set the remote ID for a user for the specified game. It is safe to
+ assume that this sampling method `works_with?` that game.
+
+ - `self.get_remote_name(Object)`
+
+ When given an object from `RemoteUsername#value`, give back a
+ human-readable/editable name to display
+
+----
+
+ - `initialize(Match)`
+
+ Construct new Sampling object for the specified match.
+
+ - `start()`
+
+ Begin fetching the statistics.
+
+ - `render_user_interaction(User) => String`
+
+ Returns HTML to render on a page.
+
+ - `handle_user_interaction(User, Hash params)`
+
+ Handles params from the form generated by
+ `#user_interaction_render`.
diff --git a/lib/sampling/double_blind.rb.bak b/lib/sampling/double_blind.rb.bak
new file mode 100644
index 0000000..6a30d57
--- /dev/null
+++ b/lib/sampling/double_blind.rb.bak
@@ -0,0 +1,35 @@
+module Sampling
+ module DoubleBlind
+ def self.works_with?(game)
+ return true
+ end
+
+ def can_get?(setting_name)
+ return 1
+ end
+
+ def self.uses_remote?
+ return false
+ end
+
+ def self.set_remote_name(user, game, value)
+ raise "This sampling method doesn't use remote usernames."
+ end
+
+ def self.get_remote_name(value)
+ raise "This sampling method doesn't use remote usernames."
+ end
+
+ def self.sampling_start(match, statistics)
+ # TODO
+ end
+
+ def self.render_user_interaction(match, user)
+ # TODO
+ end
+
+ def self.handle_user_interaction(match, user, sampling_params)
+ # TODO
+ end
+ end
+end
diff --git a/lib/sampling/manual.html.erb b/lib/sampling/manual.html.erb
new file mode 100644
index 0000000..187f002
--- /dev/null
+++ b/lib/sampling/manual.html.erb
@@ -0,0 +1,13 @@
+<% if @tournament.hosts.include? @current_user %>
+ <input type="hidden" name="update_action" value="finish" >
+ <% @match.teams.each do |team| %>
+ <label>
+ <input type="radio", name="win", value="<%= team.id %>" >
+ <%= "Team #{team.id} Won" %>
+ </label>
+ <% end %>
+ <br>
+ <input type="submit", value="Finish match" >
+<% else %>
+ <p>The match is running; the host has yet to post the scores of the match.</p>
+<% end %>
diff --git a/lib/sampling/manual.rb b/lib/sampling/manual.rb
new file mode 100644
index 0000000..01f6835
--- /dev/null
+++ b/lib/sampling/manual.rb
@@ -0,0 +1,53 @@
+module Sampling
+ class Manual
+ def self.works_with?(game)
+ return true
+ end
+
+ def self.can_get?(setting_name)
+ return 1
+ end
+
+ def self.uses_remote?
+ return false
+ end
+
+ def self.set_remote_name(user, game, value)
+ raise "This sampling method doesn't use remote usernames."
+ end
+
+ def self.get_remote_name(value)
+ raise "This sampling method doesn't use remote usernames."
+ end
+
+ ####
+
+ def initialize(match)
+ @match = match
+ end
+
+ def start
+ # do nothing
+ end
+
+ def render_user_interaction(user)
+ @tournament = @match.tournament_stage.tournament
+ @current_user = user
+ @users = @match.users
+ @stats = @match.stats_from(self.class)
+
+ require 'erb'
+ erb_filename = File.join(__FILE__.sub(/\.rb$/, '.html.erb'))
+ erb = ERB.new(File.read(erb_filename))
+ erb.filename = erb_filename
+ return erb.result(binding).html_safe
+ end
+
+ def handle_user_interaction(user, sampling_params)
+ # => Save sampling_params as statistics
+ sampling_params.select {|name, value| @match.stats_from(self.class).include? name }.each do |name, value|
+ Statistic.create(name: value, user: user, match: @match)
+ end
+ end
+ end
+end
diff --git a/lib/sampling/peer_review.html.erb b/lib/sampling/peer_review.html.erb
new file mode 100644
index 0000000..a0b9c4d
--- /dev/null
+++ b/lib/sampling/peer_review.html.erb
@@ -0,0 +1,28 @@
+<% if @feedbacks_missing.include? @user %>
+ <script type="text/javascript">
+ function score_peers() {
+ var list = $('ol#peer_review_boxes');
+ for(var i=0, var len=list.length; i < len; i++) {
+ if ( i == len-1) {
+ comma = "";
+ }
+ $('peer_review').value += $('ol#peer_review_boxes:eq(' + i + ')').text() + comma;
+ }
+ }
+ </script>
+ <input type="hidden" id="peer_review" name="peer_review" value="" />
+ <ol id="peer_review_boxes" class="sortable">
+ <% @team.users.reject{|u|u==@user}.each do |user| %><li>
+ <%= user.user_name %>
+ <br>
+ <%# TODO: display more statistics %>
+ </li><% end %>
+ </ol>
+ <input type="submit" value="Submit peer evaluation", onsubmit="score_peers()") >
+<% else %>
+ <p>Still waiting for peer feedback from the following users:
+ <ul><% @feedbacks_missing.each do |user| %>
+ <li><%= link_to user %></li>
+ <% end %></ul>
+ </p>
+<% end %>
diff --git a/lib/sampling/peer_review.rb b/lib/sampling/peer_review.rb
new file mode 100644
index 0000000..1aabe34
--- /dev/null
+++ b/lib/sampling/peer_review.rb
@@ -0,0 +1,91 @@
+module Sampling
+ class PeerReview
+ def self.works_with?(game)
+ return true
+ end
+
+ def self.can_get?(setting_name)
+ return setting_name.start_with?("feedback_from_") ? 2 : 0
+ end
+
+ def self.uses_remote?
+ return false
+ end
+
+ def self.set_remote_name(user, game, value)
+ raise "This sampling method doesn't use remote usernames."
+ end
+
+ def self.get_remote_name(value)
+ raise "This sampling method doesn't use remote usernames."
+ end
+
+ ####
+
+ def initialize(match)
+ @match = match
+ end
+
+ def start
+ # do nothing
+ end
+
+ def render_user_interaction(user)
+ @user = user
+ @team = get_team(match)
+ @feedbacks_missing = get_feedbacks_missing(match)
+
+ require 'erb'
+ erb_filename = File.join(__FILE__.sub(/\.rb$/, '.html.erb'))
+ erb = ERB.new(File.read(erb_filename))
+ erb.filename = erb_filename
+ return erb.result(binding).html_safe
+ end
+
+ def handle_user_interaction(reviewing_user, params)
+ i = 0
+ params[:peer_review].to_s.split(',').each do |user_name|
+ reviewed_user = User.find_by_user_name(user_name)
+ user.statistics.create(match: @match, value: i)
+ i += 1
+ end
+ end
+
+ private
+
+ def self.get_users(match)
+ users = []
+ match.teams.each{|t| users.concat(t.users)}
+ return users
+ end
+
+ def self.get_team(match)
+ match.teams.find{|t|t.users.include?(@user)}
+ end
+
+ def self.get_feedbacks(match)
+ ret = {}
+ match.statistiscs.where("'name' LIKE 'feedback_from_%'").each do |statistic|
+ ret[statistic.user] ||= {}
+ ret[statistic.user][User.find_by_user_name(statistic.name.sub(/^feedback_from_/,''))] = statistic.value
+ end
+ return ret
+ end
+
+ def self.get_feedbacks_missing(match)
+ require 'set'
+ ret = Set.new
+
+ feedback = get_feedbacks(match)
+ users = get_users(match)
+
+ feedback.each do |feedback|
+ (users - feedback.keys).each do |user|
+ ret.add(user)
+ end
+ end
+
+ return ret
+ end
+ end
+end
diff --git a/lib/sampling/riot_api.rb b/lib/sampling/riot_api.rb
new file mode 100644
index 0000000..bbe9cea
--- /dev/null
+++ b/lib/sampling/riot_api.rb
@@ -0,0 +1,207 @@
+module Sampling
+ class RiotApi
+ protected
+ def self.api_name
+ "prod.api.pvp.net/api/lol"
+ end
+
+ protected
+ def self.api_key
+ ENV["RIOT_API_KEY"]
+ end
+
+ protected
+ def self.region
+ ENV["RIOT_API_REGION"]
+ end
+
+ protected
+ def self.url(request, args={})
+ "https://prod.api.pvp.net/api/lol/#{region}/#{request % args.merge(args){|k,v|url_escape(v)}}?api_key=#{api_key}"
+ end
+
+ protected
+ def self.url_escape(string)
+ URI::escape(string.to_s, /[^a-zA-Z0-9._~!$&'()*+,;=:@-]/)
+ end
+
+ protected
+ def self.standardize(summoner_name)
+ summoner_name.to_s.downcase.gsub(' ', '')
+ end
+
+ protected
+ def self.stats_available
+ ["win", "numDeaths", "turretsKilled", "championsKilled", "minionsKilled", "assists"]
+ end
+
+ protected
+ class Job < ThrottledApiRequest
+ def initialize(request, args={})
+ @url = Sampling::RiotApi::url(request, args)
+ limits = [
+ {:unit_time => 10.seconds, :requests_per => 10},
+ {:unit_time => 10.minutes, :requests_per => 500},
+ ]
+ super(RiotApi::api_name, limits)
+ end
+
+ def perform
+ response = open(@url)
+ status = response.status
+ data = JSON::parse(response.read)
+
+ # Error codes that RIOT uses:
+ # "400"=>"Bad request"
+ # "401"=>"Unauthorized"
+ # "429"=>"Rate limit exceeded"
+ # "500"=>"Internal server error"
+ # "503"=>"Service unavailable"
+ # "404"=>"Not found"
+ # Should probably handle these better
+ if status[0] != "200"
+ raise "GET #{@url} => #{status.join(" ")}"
+ end
+ return self.handle(data)
+ end
+
+ def handle(data)
+ return true
+ end
+ end
+
+ ########################################################################
+
+ ##
+ # Return whether or not this sampling method works with the specified game.
+ # Spoiler: It only works with League of Legends (or subclasses of it).
+ public
+ def self.works_with?(game)
+ if api_key.nil? or region.nil?
+ return false
+ end
+ if game.name == "League of Legends"
+ return true
+ end
+ unless game.parent.nil?
+ return works_with?(game.parent)
+ end
+ end
+
+ ##
+ # Return whether or not the API can get a given statistic for
+ # a given user.
+ public
+ def self.can_get?(stat)
+ if stats_available.include?(stat)
+ return 2
+ else
+ return 0
+ end
+ end
+
+ ##
+ # This sampling method uses remote IDs
+ public
+ def self.uses_remote?
+ return true
+ end
+
+ ##
+ # When given a summoner name for a user, figure out the summoner ID.
+ public
+ def self.set_remote_name(user, game, summoner_name)
+ Delayed::Job.enqueue(UsernameJob.new(user, game, summoner_name), :queue => RiotApi::api_name)
+ end
+ protected
+ class UsernameJob < Job
+ def initialize(user, game, summoner_name)
+ @user_id = user.id
+ @game_id = game.id
+ # Escape any funny stuff
+ summoner_names = [summoner_name].map{|name|Sampling::RiotApi::standardize(name.gsub(',',''))}
+ # Generate the request
+ super("v1.3/summoner/by-name/%{summonerNames}", { :summonerNames => summoner_names.join(",") })
+ end
+ def handle(data)
+ user = User.find(@user_id)
+ game = Game.find(@game_id)
+
+ standardized_summoner_name = data.keys.first
+ remote_data = {
+ :id => data[standardized_summoner_name]["id"],
+ :name => data[standardized_summoner_name]["name"],
+ }
+
+ user.set_remote_username(game, remote_data)
+ end
+ end
+
+ ##
+ # When given data from RemoteUsername#value, give back a readable name to display.
+ # Here, this is the summoner name.
+ public
+ def self.get_remote_name(data)
+ data["name"]
+ end
+
+ ####
+
+ public
+ def initialize(match)
+ @match = match
+ end
+
+ ##
+ # Fetch all the statistics for a match.
+ public
+ def start
+ @match.teams.each do |team|
+ team.users.each do |user|
+ #For demo purposes, we are hard coding in a league of legends game id.
+ Delayed::Job.enqueue(FetchStatisticsJob.new(user, @match, @match.stats_from(self.class), 10546), :queue => RiotApi::api_name)
+ end
+ end
+ end
+ protected
+ class FetchStatisticsJob < Job
+ def initialize(user, match, stats, last_game_id)
+ @user_id = user.id
+ @match_id = match.id
+ @stats = stats
+ @last_game_id = last_game_id
+
+ # Get the summoner id
+ summoner = user.get_remote_username(match.tournament_stage.tournament.game)
+ # Generate the request
+ super("v1.3/game/by-summoner/%{summonerId}/recent", { :summonerId => summoner["id"] })
+ end
+ def handle(data)
+ user = User.find(@user_id)
+ match = Match.find(@match_id)
+ if @last_game_id.nil?
+ Delayed::Job.enqueue(FetchStatisticsJob.new(user, match, data["games"][0]["gameId"]), :queue => RiotApi::api_name)
+ else
+ if @last_game_id == data["games"][0]["gameId"]
+ sleep(4.minutes)
+ Delayed::Job.enqueue(FetchStatisticsJob.new(user, match, @last_game_id), :queue => RiotApi::api_name)
+ else
+ @stats.each do |stat|
+ Statistic.create(user: user, match: match, name: stat, value: data["games"][0]["stats"][stat])
+ end
+ end
+ end
+ end
+ end
+
+ public
+ def render_user_interaction(user)
+ return ""
+ end
+
+ public
+ def handle_user_interaction(user)
+ # do nothing
+ end
+ end
+end
diff --git a/lib/scheduling/README.md b/lib/scheduling/README.md
new file mode 100644
index 0000000..fe6aba1
--- /dev/null
+++ b/lib/scheduling/README.md
@@ -0,0 +1,22 @@
+Scheduling interface
+====================
+
+Files in this directory should be _classes_ implementing the following
+interface:
+
+ - `initialize(TournamentStage)`
+
+ Construct new Scheduling object from tournament_stage.
+
+ - `create_matches`
+
+ Creates all the matches of the current round.
+
+ - `finish_match(Match)`
+
+ Progresses the match through the schedule.
+
+ - `graph`
+
+ Returns a string representation of an svg image of the current
+ stage.
diff --git a/lib/scheduling/elimination.rb b/lib/scheduling/elimination.rb
new file mode 100644
index 0000000..73aefb4
--- /dev/null
+++ b/lib/scheduling/elimination.rb
@@ -0,0 +1,145 @@
+
+module Scheduling
+ class Elimination
+ include Rails.application.routes.url_helpers
+
+ def initialize(tournament_stage)
+ @tournament_stage = tournament_stage
+ end
+
+ def create_matches
+ num_teams = (tournament.players.count/tournament.min_players_per_team).floor
+ num_matches = (Float(num_teams - tournament.min_teams_per_match)/(tournament.min_teams_per_match - 1)).ceil + 1
+ for i in 1..num_matches
+ tournament_stage.matches.create(status: 0, submitted_peer_evaluations: 0)
+ end
+
+ match_num = num_matches-1
+ team_num = 0
+
+ tournament.players.shuffle
+
+ # for each grouping of min_players_per_team
+ tournament.players.each_slice(tournament.min_players_per_team) do |team_members|
+ # if the match is full, move to the next match, otherwise move to the next team
+ if (team_num == tournament.min_teams_per_match)
+ match_num -= 1
+ team_num = 1
+ else
+ team_num += 1
+ end
+ # create a new team in the current match
+ tournament_stage.matches[match_num].teams.push(Team.create(users: team_members))
+ end
+ end
+
+ def finish_match(match)
+ matches = match.tournament_stage.matches_ordered
+ cur_match_num = matches.invert[match]
+ unless cur_match_num == 1
+ match.winner.matches.push(matches[cur_match_num/2])
+ end
+ end
+
+ def graph(current_user)
+ matches = @tournament_stage.matches_ordered
+ numTeams = @tournament_stage.tournament.min_teams_per_match
+ logBase = numTeams
+
+ # depth of SVG tree
+ depth = Math.log(matches.count*(logBase-1),logBase).floor+1;
+
+ # height of SVG
+ matchHeight = 50*logBase;
+ height = [(matchHeight+50) * logBase**(depth-1) + 100, 500].max;
+ height = height/2;
+
+ str = <<-STRING
+ <svg version="1.1" baseProfile="full"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="100%" height="#{height}">
+ <defs>
+ <radialGradient id="gradMatch" cx="50%" cy="50%" r="80%" fx="50%" fy="50%">
+ <stop offset="0%" style="stop-color:#fff; stop-opacity:1" />
+ <stop offset="100%" style="stop-color:#ccc;stop-opacity:0" />
+ </radialGradient>
+ </defs>
+ STRING
+ base = 1
+ pBase = 1
+ (1..matches.count).each do |i|
+ matchDepth = Math.log(i*(logBase-1), logBase).floor+1
+ puts matchDepth
+ if matchDepth > Math.log(base*(logBase-1), logBase).floor+1
+ pBase = base
+ base = i
+ end
+ puts base
+ rh = 100 / (logBase**(depth-1)+1) - 100/height;
+ puts rh
+ rw = 100/(depth+1) - 5
+ puts rw
+ rx = 50/(depth+1) + 100/(depth+1)*(depth-matchDepth)
+ puts rx
+ ry = 100/(logBase**(matchDepth-1)+1) * (i-base+1) - rh/2
+ puts ry
+
+ str += "\t<a id=\"svg-match-#{i}\" xlink:href=\"#{match_path(matches[i])}\"><g>\n"
+ str += "\t\t<rect height=\"#{rh}%\" width=\"#{rw}%\" x=\"#{rx}%\" y=\"#{ry}%\" fill=\"url(#gradMatch)\" rx=\"5px\" stroke-width=\"2\""
+ case matches[i].status
+ when 0
+ if matches[i].teams.count < @tournament_stage.tournament.min_teams_per_match
+ str += ' stroke="red"'
+ str += ' fill-opacity="0.6"'
+ else
+ str += ' stroke="green"'
+ end
+ when 1
+ str += ' stroke="orange"'
+ when 2
+ str += ' stroke="yellow"'
+ when 3
+ str += ' stroke="grey"'
+ end
+
+ str += "/>\n"
+
+ t = 1
+ while t <= numTeams
+ color = (matches[i].teams[t-1] and matches[i].teams[t-1].users.include?(current_user)) ? "#5BC0DE" : "white"
+ str += "\t\t<rect width=\"#{rw-5}%\" height=\"#{rh*Float(30)/(matchHeight)}%\" x=\"#{rx + 2.5}%\" y=\"#{ry + (Float(t-1)/numTeams)*rh + 1 }%\" fill=\"#{color}\" />\n"
+ if matches[i].teams[t-1]
+ str += "\t\t<text x=\"#{rx + rw/4}%\" y=\"#{ry + (Float(t-1)/numTeams + Float(33)/(matchHeight))*rh}%\" font-size=\"120%\">Team #{matches[i].teams[t-1].id}</text>\n"
+ end
+ if (t < numTeams)
+ str += "\t\t<text x=\"#{rx + 1.3*rw/3}%\" y=\"#{ry + (Float(t)/numTeams)*rh + 1}%\" font-size=\"120%\"> VS </text>\n"
+ end
+ t = t + 1
+ end
+
+ if i > 1
+ parent = (i+logBase-2)/logBase
+ pDepth = Math.log(parent*(logBase-1), logBase).floor+1
+ lastrx = 50/(depth+1) + 100/(depth+1)*(depth-pDepth)
+ lastry = 100/(logBase**(pDepth-1)+1) * (parent-pBase+1) - rh/2
+ str += "\t\t<line x1=\"#{rx+rw}%\" y1=\"#{ry+rh/2}%\" x2=\"#{lastrx}%\" y2=\"#{lastry+rh/2}%\" stroke=\"white\" stroke-width=\"2\" >\n"
+ end
+ str += "</g></a>\n"
+ end
+ str += '</svg>'
+
+ return str
+ end
+
+ private
+
+ def tournament_stage
+ @tournament_stage
+ end
+
+ def tournament
+ tournament_stage.tournament
+ end
+ end
+end
diff --git a/lib/scheduling/round_robin.rb b/lib/scheduling/round_robin.rb
new file mode 100644
index 0000000..c0d68d3
--- /dev/null
+++ b/lib/scheduling/round_robin.rb
@@ -0,0 +1,70 @@
+# http://stackoverflow.com/questions/6648512/scheduling-algorithm-for-a-round-robin-tournament
+module Scheduling
+ class RoundRobin
+ include Rails.application.routes.url_helpers
+
+ def initialize(tournament_stage)
+ @tournament_stage = tournament_stage
+ end
+
+ def create_matches
+ # => find the number of matches and teams to create
+ @num_teams = (tournament.players.count/tournament.min_players_per_team).floor
+ @matches_per_round = (@num_teams / tournament.min_teams_per_match).floor
+
+ # => initialize data and status members
+ @team_pairs ||= Array.new
+ if @team_pairs.empty?
+ @matches_finished = 0
+ end
+
+ # => Create new matches
+ @matches_per_round.times do
+ tournament_stage.matches.create(status: 0, submitted_peer_evaluations: 0)
+ end
+
+ # => seed the first time
+ if @team_pairs.empty?
+ tournament_stage.seeding.seed(tournament_stage)
+ tournament_stage.matches.each {|match| match.teams.each {|team| @team_pairs.push team}}
+ else
+ # => Reorder the list of teams
+ top = @team_pairs.shift
+ @team_pairs.push @team_pairs.shift
+ @team_pairs.unshift top
+
+ # => Add the teams to the matches
+ match = tournament_stage.matches[@matches_finished-1]
+ matches = 1
+ (0..@team_pairs.count-1).each do |i|
+ match.teams += @team_pairs[i]
+ if @team_pairs.count.%(tournament.min_teams_per_match).zero?
+ match = tournament_stage.matches[@matches_finished-1 + matches]
+ matches += 1
+ end
+ end
+
+ end
+
+ # => Set the match statuses to ready (1)
+ tournament_stage.matches.each {|match| match.update(status: 1)}
+
+ end
+
+ def finish_match(match)
+ @matches_finished += 1
+ end
+
+ def graph(current_user)
+ end
+
+ private
+ def tournament_stage
+ @tournament_stage
+ end
+
+ def tournament
+ tournament_stage.tournament
+ end
+ end
+end
diff --git a/lib/scoring/README.md b/lib/scoring/README.md
new file mode 100644
index 0000000..dce71d0
--- /dev/null
+++ b/lib/scoring/README.md
@@ -0,0 +1,15 @@
+Scoring interface
+=================
+
+Files in this directory should be _modules_ implementing the following
+interface:
+
+ - `stats_needed(Match) => Array[]=Symbol`
+
+ Returns which statistics need to be collected for this scoring
+ algorithm.
+
+ - `score(Match) => Hash[User]=Integer`
+
+ User scores for this match, assuming statistics have been
+ collected.
diff --git a/lib/scoring/fibonacci_peer_with_blowout.rb b/lib/scoring/fibonacci_peer_with_blowout.rb
new file mode 100644
index 0000000..f592540
--- /dev/null
+++ b/lib/scoring/fibonacci_peer_with_blowout.rb
@@ -0,0 +1,28 @@
+module Scoring
+ module FibonacciPeerWithBlowout
+ def self.stats_needed
+ return [:votes, :win, :blowout]
+ end
+
+ def self.score(match)
+ scores = {}
+ match.players.each do |player|
+ stats = Statistics.where(user: player, match: match)
+
+ votes = stats.where(name: :votes ).first
+ win = stats.where(name: :win ).first
+ blowout = stats.where(name: :blowout).first
+
+ scores[player] = self.score_user(votes, win, blowout)
+ end
+ scores
+ end
+
+ protected
+
+ def self.score_user(votes, win, blowout)
+ fibonacci = Hash.new { |h,k| h[k] = k < 2 ? k : h[k-1] + h[k-2] }
+ fibonacci[votes+3] + (win ? blowout ? 12 : 10 : blowout ? 5 : 7)
+ end
+ end
+end
diff --git a/lib/scoring/marginal_peer.rb b/lib/scoring/marginal_peer.rb
new file mode 100644
index 0000000..aa05f5e
--- /dev/null
+++ b/lib/scoring/marginal_peer.rb
@@ -0,0 +1,15 @@
+module Scoring
+ module MarginalPeer
+ def self.stats_needed
+ return [:rating]
+ end
+
+ def self.score(match, interface)
+ scores = {}
+ match.players.each do |player|
+ scores[player.user_name] = interface.get_statistic(match, player, :rating)
+ end
+ scores
+ end
+ end
+end
diff --git a/lib/scoring/winner_takes_all.rb b/lib/scoring/winner_takes_all.rb
new file mode 100644
index 0000000..57ddae6
--- /dev/null
+++ b/lib/scoring/winner_takes_all.rb
@@ -0,0 +1,20 @@
+module Scoring
+ module WinnerTakesAll
+ def self.stats_needed
+ return ["win"]
+ end
+
+ def self.score(match, interface)
+ scores = {}
+ match.players.each do |player|
+ scores[player.user_name] = score_user(player.statistics.where(:match => match, :name => "win").value)
+ end
+ scores
+ end
+
+ private
+ def self.score_user(win)
+ win.nil? ? 0.5 : win ? 1 : 0
+ end
+ end
+end
diff --git a/lib/seeding/.keep b/lib/seeding/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/seeding/.keep
diff --git a/lib/seeding/README.md b/lib/seeding/README.md
new file mode 100644
index 0000000..d323b6d
--- /dev/null
+++ b/lib/seeding/README.md
@@ -0,0 +1,10 @@
+Seeding interface
+=================
+
+Files in this directory should be _modules_ implement the following
+interface:
+
+ - `seed(TournamentStage)`
+
+ Take a tournament stage, assign players to teams and teams to
+ matches (matches must exist).
diff --git a/lib/seeding/early_bird_seeding.rb b/lib/seeding/early_bird_seeding.rb
new file mode 100644
index 0000000..bf7b3c2
--- /dev/null
+++ b/lib/seeding/early_bird_seeding.rb
@@ -0,0 +1,20 @@
+module Seeding
+ module EarlyBirdSeeding
+ def self.seed(tournament_stage)
+ matches = tournament_stage.matches
+ match = matches.first
+ match_num = 0
+ teams = 0
+ tournament_stage.tournament.players.each_slice(tournament_stage.tournament.min_players_per_team) do |slice|
+ if teams < tournament_stage.tournament.min_teams_per_match
+ match.teams.push Team.create(players: slice)
+ teams += 1
+ else
+ match_num += 1
+ match = matches[match_num]
+ teams = 0
+ end
+ end
+ end
+ end
+end
diff --git a/lib/seeding/fair_ranked_seeding.rb b/lib/seeding/fair_ranked_seeding.rb
new file mode 100644
index 0000000..870ebdd
--- /dev/null
+++ b/lib/seeding/fair_ranked_seeding.rb
@@ -0,0 +1,43 @@
+module Seeding
+ module FairRankedSeeding
+ def self.seed(tournament_stage)
+ matches = tournament.current_stage.matches
+ match = matches.first
+ match_num = 0
+ players_used = 0
+ (tournament.players.count/tournament.min_players_per_team).floor.times do
+ match.teams.push Team.create()
+ end
+ best_first(tournament).each_slice(tournament.min_teams_per_match) do |slice|
+ (0..tournament.min_teams_per_match-1).each do |index|
+ match.teams[index].players += slice[index]
+ end
+ players_used += 1
+ if players_used == tournament.min_players_per_team
+ match_num += 1
+ match = matches[match_num]
+ players_used = 0
+ end
+ end
+ end
+
+ private
+ def self.best_first(tournament)
+ tournament.players.sort {|a, b| better(a, b, tournament) }
+ end
+
+ def self.better(player1, player2, tournament)
+ ps1 = previous_score(player1, tournament)
+ ps2 = previous_score(player2, tournament)
+ ps1 <=> ps2
+ end
+
+ def self.previous_score(player, tournament)
+ score = tournament.statistics.where(match: player.matches.last, user: player, name: :score)
+ if score.nil?
+ return 0
+ end
+ score
+ end
+ end
+end
diff --git a/lib/seeding/random_seeding.rb b/lib/seeding/random_seeding.rb
new file mode 100644
index 0000000..ccdba11
--- /dev/null
+++ b/lib/seeding/random_seeding.rb
@@ -0,0 +1,20 @@
+module Seeding
+ module RandomSeeding
+ def self.seed(tournament_stage)
+ matches = tournament_stage.matches
+ match = matches.first
+ match_num = 0
+ teams = 0
+ tournament_stage.tournament.players.shuffle.each_slice(tournament_stage.tournament.min_players_per_team) do |slice|
+ if teams < tournament_stage.tournament.min_teams_per_match
+ match.teams.push Team.create(players: slice)
+ teams += 1
+ else
+ match_num += 1
+ match = matches[match_num]
+ teams = 0
+ end
+ end
+ end
+ end
+end
diff --git a/lib/throttled_api_request.rb b/lib/throttled_api_request.rb
new file mode 100644
index 0000000..c48a66d
--- /dev/null
+++ b/lib/throttled_api_request.rb
@@ -0,0 +1,32 @@
+# limits is in the format:
+# limits = [
+# {:unit_time => 10.seconds, :requests_per => 10},
+# {:unit_time => 10.minutes, :requests_per => 500},
+# ]
+class ThrottledApiRequest < Struct.new(:api_name, :limits)
+ def before(job)
+ loop do
+ sleep_for = -1
+ ActiveRecord::Base.transaction do
+ ApiRequest.create(:api_name => self.api_name)
+ self.limits.each do |limit|
+ recent_requests = ApiRequest.
+ where(:api_name => self.api_name).
+ where("updated_at > ?", Time.now.utc - limit[:unit_time]).
+ order(:updated_at)
+ if (recent_requests.count > limit[:requests_per])
+ sleep_for = [sleep_for, Time.now.utc - recent_requests[recent_requests.count-limit[:requests_per]].updated_at].max
+ end
+ end
+ if sleep_for != -1
+ raise ActiveRecord::Rollback
+ end
+ end
+ if sleep_for != -1
+ sleep(sleep_for)
+ else
+ break
+ end
+ end
+ end
+end
diff --git a/purple.yaml b/purple.yaml
new file mode 100644
index 0000000..0413cde
--- /dev/null
+++ b/purple.yaml
@@ -0,0 +1,194 @@
+---
+Sytrie:
+ level: 16
+ goldEarned: 9257
+ numDeaths: 7
+ turretsKilled: 1
+ minionsKilled: 57
+ championsKilled: 4
+ goldSpent: 8025
+ totalDamageDealt: 71437
+ totalDamageTaken: 20413
+ killingSprees: 1
+ largestKillingSpree: 2
+ team: 200
+ win: false
+ largestMultiKill: 1
+ physicalDamageDealtPlayer: 15947
+ magicDamageDealtPlayer: 55489
+ physicalDamageTaken: 12706
+ magicDamageTaken: 7567
+ timePlayed: 2437
+ totalHeal: 566
+ totalUnitsHealed: 1
+ assists: 11
+ item0: 3027
+ item1: 3020
+ item2: 1056
+ item3: 3135
+ item4: 1026
+ item6: 3340
+ sightWardsBought: 3
+ visionWardsBought: 1
+ magicDamageDealtToChampions: 19109
+ physicalDamageDealtToChampions: 1682
+ totalDamageDealtToChampions: 20791
+ trueDamageTaken: 140
+ wardPlaced: 12
+ totalTimeCrowdControlDealt: 77
+Derpanator115:
+ level: 17
+ goldEarned: 13648
+ numDeaths: 7
+ turretsKilled: 2
+ minionsKilled: 218
+ championsKilled: 10
+ goldSpent: 12885
+ totalDamageDealt: 161790
+ totalDamageTaken: 27265
+ killingSprees: 3
+ largestKillingSpree: 4
+ team: 200
+ win: false
+ largestMultiKill: 1
+ physicalDamageDealtPlayer: 158065
+ magicDamageDealtPlayer: 3724
+ physicalDamageTaken: 16740
+ magicDamageTaken: 10176
+ largestCriticalStrike: 834
+ timePlayed: 2437
+ totalHeal: 2442
+ totalUnitsHealed: 1
+ assists: 12
+ item0: 3035
+ item1: 3072
+ item2: 3250
+ item3: 3031
+ item4: 1028
+ item6: 3340
+ magicDamageDealtToChampions: 2286
+ physicalDamageDealtToChampions: 21244
+ totalDamageDealtToChampions: 23531
+ trueDamageTaken: 348
+ wardKilled: 2
+ wardPlaced: 9
+ totalTimeCrowdControlDealt: 90
+Wlknexe56:
+ level: 16
+ goldEarned: 11547
+ numDeaths: 4
+ minionsKilled: 144
+ championsKilled: 10
+ goldSpent: 12285
+ totalDamageDealt: 112047
+ totalDamageTaken: 19254
+ killingSprees: 3
+ largestKillingSpree: 5
+ team: 200
+ win: false
+ largestMultiKill: 1
+ physicalDamageDealtPlayer: 101821
+ magicDamageDealtPlayer: 8983
+ physicalDamageTaken: 10827
+ magicDamageTaken: 7883
+ largestCriticalStrike: 805
+ timePlayed: 2437
+ totalHeal: 1526
+ totalUnitsHealed: 1
+ assists: 10
+ item0: 3270
+ item1: 3035
+ item2: 3074
+ item3: 3071
+ item4: 1031
+ item6: 3340
+ magicDamageDealtToChampions: 3039
+ physicalDamageDealtToChampions: 21523
+ totalDamageDealtToChampions: 25804
+ trueDamageDealtPlayer: 1242
+ trueDamageDealtToChampions: 1242
+ trueDamageTaken: 544
+ wardPlaced: 10
+ totalTimeCrowdControlDealt: 49
+DVisionzz:
+ level: 18
+ goldEarned: 12520
+ numDeaths: 5
+ barracksKilled: 2
+ turretsKilled: 1
+ minionsKilled: 53
+ championsKilled: 12
+ goldSpent: 11353
+ totalDamageDealt: 156993
+ totalDamageTaken: 25675
+ doubleKills: 1
+ killingSprees: 2
+ largestKillingSpree: 7
+ team: 200
+ win: false
+ neutralMinionsKilled: 69
+ largestMultiKill: 2
+ physicalDamageDealtPlayer: 113041
+ magicDamageDealtPlayer: 34822
+ physicalDamageTaken: 21062
+ magicDamageTaken: 4546
+ largestCriticalStrike: 595
+ timePlayed: 2437
+ totalHeal: 6278
+ totalUnitsHealed: 1
+ assists: 4
+ item0: 3160
+ item1: 3260
+ item2: 3078
+ item3: 1036
+ item4: 3172
+ item5: 1011
+ item6: 3340
+ magicDamageDealtToChampions: 2229
+ physicalDamageDealtToChampions: 17146
+ totalDamageDealtToChampions: 19606
+ trueDamageDealtPlayer: 9129
+ trueDamageDealtToChampions: 231
+ trueDamageTaken: 66
+ wardPlaced: 13
+ neutralMinionsKilledEnemyJungle: 26
+ neutralMinionsKilledYourJungle: 43
+ totalTimeCrowdControlDealt: 180
+HYP3RTONIC:
+ level: 17
+ goldEarned: 10739
+ numDeaths: 3
+ minionsKilled: 101
+ championsKilled: 9
+ goldSpent: 10275
+ totalDamageDealt: 80887
+ totalDamageTaken: 22178
+ killingSprees: 1
+ largestKillingSpree: 8
+ team: 200
+ win: false
+ neutralMinionsKilled: 9
+ largestMultiKill: 1
+ physicalDamageDealtPlayer: 31149
+ magicDamageDealtPlayer: 49738
+ physicalDamageTaken: 13555
+ magicDamageTaken: 7847
+ timePlayed: 2437
+ totalHeal: 1514
+ totalUnitsHealed: 1
+ assists: 11
+ item0: 1056
+ item1: 3255
+ item2: 3135
+ item3: 3027
+ item4: 3003
+ item5: 1028
+ item6: 3340
+ magicDamageDealtToChampions: 17867
+ physicalDamageDealtToChampions: 3182
+ totalDamageDealtToChampions: 21050
+ trueDamageTaken: 776
+ wardPlaced: 8
+ neutralMinionsKilledEnemyJungle: 6
+ neutralMinionsKilledYourJungle: 3
+ totalTimeCrowdControlDealt: 62
diff --git a/start b/start
new file mode 100755
index 0000000..9ee8eba
--- /dev/null
+++ b/start
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+if [ $1 == "-c" ]; then
+ bundle exec rails c
+fi
+if [ $1 == "-s" ]; then
+ bundle exec rails s
+fi
+
+#echo "Only use: start -c or start -s. Don't be a jackass."
+
diff --git a/stop.sh b/stop.sh
new file mode 100755
index 0000000..719ed94
--- /dev/null
+++ b/stop.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+kill $(<tmp/pids/server.pid)
diff --git a/vendor/assets/javascripts/coordinates.js b/vendor/assets/javascripts/coordinates.js
new file mode 100644
index 0000000..e2f5bf2
--- /dev/null
+++ b/vendor/assets/javascripts/coordinates.js
@@ -0,0 +1,94 @@
+var Coordinates = {
+ ORIGIN : new Coordinate(0, 0),
+
+ northwestPosition : function(element) {
+ var x = parseInt(element.style.left);
+ var y = parseInt(element.style.top);
+
+ return new Coordinate(isNaN(x) ? 0 : x, isNaN(y) ? 0 : y);
+ },
+
+ southeastPosition : function(element) {
+ return Coordinates.northwestPosition(element).plus(
+ new Coordinate(element.offsetWidth, element.offsetHeight));
+ },
+
+ northwestOffset : function(element, isRecursive) {
+ var offset = new Coordinate(element.offsetLeft, element.offsetTop);
+
+ if (!isRecursive) return offset;
+
+ var parent = element.offsetParent;
+ while (parent) {
+ offset = offset.plus(
+ new Coordinate(parent.offsetLeft, parent.offsetTop));
+ parent = parent.offsetParent;
+ }
+ return offset;
+ },
+
+ southeastOffset : function(element, isRecursive) {
+ return Coordinates.northwestOffset(element, isRecursive).plus(
+ new Coordinate(element.offsetWidth, element.offsetHeight));
+ },
+
+ fixEvent : function(event) {
+ event.windowCoordinate = new Coordinate(event.clientX, event.clientY);
+ }
+};
+
+function Coordinate(x, y) {
+ this.x = x;
+ this.y = y;
+}
+
+Coordinate.prototype.toString = function() {
+ return "(" + this.x + "," + this.y + ")";
+}
+
+Coordinate.prototype.plus = function(that) {
+ return new Coordinate(this.x + that.x, this.y + that.y);
+}
+
+Coordinate.prototype.minus = function(that) {
+ return new Coordinate(this.x - that.x, this.y - that.y);
+}
+
+Coordinate.prototype.distance = function(that) {
+ var deltaX = this.x - that.x;
+ var deltaY = this.y - that.y;
+
+ return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
+}
+
+Coordinate.prototype.max = function(that) {
+ var x = Math.max(this.x, that.x);
+ var y = Math.max(this.y, that.y);
+ return new Coordinate(x, y);
+}
+
+Coordinate.prototype.constrain = function(min, max) {
+ if (min.x > max.x || min.y > max.y) return this;
+
+ var x = this.x;
+ var y = this.y;
+
+ if (min.x != null) x = Math.max(x, min.x);
+ if (max.x != null) x = Math.min(x, max.x);
+ if (min.y != null) y = Math.max(y, min.y);
+ if (max.y != null) y = Math.min(y, max.y);
+
+ return new Coordinate(x, y);
+}
+
+Coordinate.prototype.reposition = function(element) {
+ element.style["top"] = this.y + "px";
+ element.style["left"] = this.x + "px";
+}
+
+Coordinate.prototype.equals = function(that) {
+ if (this == that) return true;
+ if (!that || that == null) return false;
+
+ return this.x == that.x && this.y == that.y;
+}
diff --git a/vendor/assets/javascripts/drag.js b/vendor/assets/javascripts/drag.js
new file mode 100644
index 0000000..9ba6746
--- /dev/null
+++ b/vendor/assets/javascripts/drag.js
@@ -0,0 +1,229 @@
+/*
+ * drag.js - click & drag DOM elements
+ *
+ * originally based on Youngpup's dom-drag.js, www.youngpup.net
+ */
+
+var Drag = {
+ BIG_Z_INDEX : 10000,
+ group : null,
+ isDragging : false,
+
+ makeDraggable : function(group) {
+ group.handle = group;
+ group.handle.group = group;
+
+ group.minX = null;
+ group.minY = null;
+ group.maxX = null;
+ group.maxY = null;
+ group.threshold = 0;
+ group.thresholdY = 0;
+ group.thresholdX = 0;
+
+ group.onDragStart = new Function();
+ group.onDragEnd = new Function();
+ group.onDrag = new Function();
+
+ // TODO: use element.prototype.myFunc
+ group.setDragHandle = Drag.setDragHandle;
+ group.setDragThreshold = Drag.setDragThreshold;
+ group.setDragThresholdX = Drag.setDragThresholdX;
+ group.setDragThresholdY = Drag.setDragThresholdY;
+ group.constrain = Drag.constrain;
+ group.constrainVertical = Drag.constrainVertical;
+ group.constrainHorizontal = Drag.constrainHorizontal;
+
+ group.onmousedown = Drag.onMouseDown;
+ },
+
+ constrainVertical : function() {
+ var nwOffset = Coordinates.northwestOffset(this, true);
+ this.minX = nwOffset.x;
+ this.maxX = nwOffset.x;
+ },
+
+ constrainHorizontal : function() {
+ var nwOffset = Coordinates.northwestOffset(this, true);
+ this.minY = nwOffset.y;
+ this.maxY = nwOffset.y;
+ },
+
+ constrain : function(nwPosition, sePosition) {
+ this.minX = nwPosition.x;
+ this.minY = nwPosition.y;
+ this.maxX = sePosition.x;
+ this.maxY = sePosition.y;
+ },
+
+ setDragHandle : function(handle) {
+ if (handle && handle != null)
+ this.handle = handle;
+ else
+ this.handle = this;
+
+ this.handle.group = this;
+ this.onmousedown = null;
+ this.handle.onmousedown = Drag.onMouseDown;
+ },
+
+ setDragThreshold : function(threshold) {
+ if (isNaN(parseInt(threshold))) return;
+
+ this.threshold = threshold;
+ },
+
+ setDragThresholdX : function(threshold) {
+ if (isNaN(parseInt(threshold))) return;
+
+ this.thresholdX = threshold;
+ },
+
+ setDragThresholdY : function(threshold) {
+ if (isNaN(parseInt(threshold))) return;
+
+ this.thresholdY = threshold;
+ },
+
+ onMouseDown : function(event) {
+ event = Drag.fixEvent(event);
+ Drag.group = this.group;
+
+ var group = this.group;
+ var mouse = event.windowCoordinate;
+ var nwOffset = Coordinates.northwestOffset(group, true);
+ var nwPosition = Coordinates.northwestPosition(group);
+ var sePosition = Coordinates.southeastPosition(group);
+ var seOffset = Coordinates.southeastOffset(group, true);
+
+ group.originalOpacity = group.style.opacity;
+ group.originalZIndex = group.style.zIndex;
+ group.initialWindowCoordinate = mouse;
+ // TODO: need a better name, but don't yet understand how it
+ // participates in the magic while dragging
+ group.dragCoordinate = mouse;
+
+ Drag.showStatus(mouse, nwPosition, sePosition, nwOffset, seOffset);
+
+ group.onDragStart(nwPosition, sePosition, nwOffset, seOffset);
+
+ // TODO: need better constraint API
+ if (group.minX != null)
+ group.minMouseX = mouse.x - nwPosition.x +
+ group.minX - nwOffset.x;
+ if (group.maxX != null)
+ group.maxMouseX = group.minMouseX + group.maxX - group.minX;
+
+ if (group.minY != null)
+ group.minMouseY = mouse.y - nwPosition.y +
+ group.minY - nwOffset.y;
+ if (group.maxY != null)
+ group.maxMouseY = group.minMouseY + group.maxY - group.minY;
+
+ group.mouseMin = new Coordinate(group.minMouseX, group.minMouseY);
+ group.mouseMax = new Coordinate(group.maxMouseX, group.maxMouseY);
+
+ document.onmousemove = Drag.onMouseMove;
+ document.onmouseup = Drag.onMouseUp;
+
+ return false;
+ },
+
+ showStatus : function(mouse, nwPosition, sePosition, nwOffset, seOffset) {
+ window.status =
+ "mouse: " + mouse.toString() + " " +
+ "NW pos: " + nwPosition.toString() + " " +
+ "SE pos: " + sePosition.toString() + " " +
+ "NW offset: " + nwOffset.toString() + " " +
+ "SE offset: " + seOffset.toString();
+ },
+
+ onMouseMove : function(event) {
+ event = Drag.fixEvent(event);
+ var group = Drag.group;
+ var mouse = event.windowCoordinate;
+ var nwOffset = Coordinates.northwestOffset(group, true);
+ var nwPosition = Coordinates.northwestPosition(group);
+ var sePosition = Coordinates.southeastPosition(group);
+ var seOffset = Coordinates.southeastOffset(group, true);
+
+ Drag.showStatus(mouse, nwPosition, sePosition, nwOffset, seOffset);
+
+ if (!Drag.isDragging) {
+ if (group.threshold > 0) {
+ var distance = group.initialWindowCoordinate.distance(
+ mouse);
+ if (distance < group.threshold) return true;
+ } else if (group.thresholdY > 0) {
+ var deltaY = Math.abs(group.initialWindowCoordinate.y - mouse.y);
+ if (deltaY < group.thresholdY) return true;
+ } else if (group.thresholdX > 0) {
+ var deltaX = Math.abs(group.initialWindowCoordinate.x - mouse.x);
+ if (deltaX < group.thresholdX) return true;
+ }
+
+ Drag.isDragging = true;
+ group.style["zIndex"] = Drag.BIG_Z_INDEX;
+ group.style["opacity"] = 0.75;
+ }
+
+ // TODO: need better constraint API
+ var adjusted = mouse.constrain(group.mouseMin, group.mouseMax);
+ nwPosition = nwPosition.plus(adjusted.minus(group.dragCoordinate));
+ nwPosition.reposition(group);
+ group.dragCoordinate = adjusted;
+
+ // once dragging has started, the position of the group
+ // relative to the mouse should stay fixed. They can get out
+ // of sync if the DOM is manipulated while dragging, so we
+ // correct the error here
+ //
+ // TODO: what we really want to do is find the offset from
+ // our corner to the mouse coordinate and adjust to keep it
+ // the same
+ var offsetBefore = Coordinates.northwestOffset(group);
+ group.onDrag(nwPosition, sePosition, nwOffset, seOffset);
+ var offsetAfter = Coordinates.northwestOffset(group);
+
+ if (!offsetBefore.equals(offsetAfter)) {
+ var errorDelta = offsetBefore.minus(offsetAfter);
+ nwPosition = Coordinates.northwestPosition(group).plus(errorDelta);
+ nwPosition.reposition(group);
+ }
+
+ return false;
+ },
+
+ onMouseUp : function(event) {
+ event = Drag.fixEvent(event);
+ var group = Drag.group;
+
+ var mouse = event.windowCoordinate;
+ var nwOffset = Coordinates.northwestOffset(group, true);
+ var nwPosition = Coordinates.northwestPosition(group);
+ var sePosition = Coordinates.southeastPosition(group);
+ var seOffset = Coordinates.southeastOffset(group, true);
+
+ document.onmousemove = null;
+ document.onmouseup = null;
+ group.onDragEnd(nwPosition, sePosition, nwOffset, seOffset);
+
+ if (Drag.isDragging) {
+ // restoring zIndex before opacity avoids visual flicker in Firefox
+ group.style["zIndex"] = group.originalZIndex;
+ group.style["opacity"] = group.originalOpacity;
+ }
+
+ Drag.group = null;
+ Drag.isDragging = false;
+
+ return false;
+ },
+
+ fixEvent : function(event) {
+ if (typeof event == 'undefined') event = window.event;
+ Coordinates.fixEvent(event);
+
+ return event;
+ }
+};
diff --git a/vendor/assets/javascripts/dragsort.js b/vendor/assets/javascripts/dragsort.js
new file mode 100644
index 0000000..6356663
--- /dev/null
+++ b/vendor/assets/javascripts/dragsort.js
@@ -0,0 +1,234 @@
+// TODO: refactor away duplicationg in DragSort and DragSortX
+
+var BetterDragSort = {
+
+ makeListSortable : function(list) {
+ var items = list.getElementsByTagName("li");
+
+ for (var i = 0; i < items.length; i++) {
+ BetterDragSort.makeItemSortable(items[i]);
+ }
+ },
+
+ makeItemSortable : function(item) {
+ Drag.makeDraggable(item);
+ item.setDragThresholdY(5);
+
+ item.onDragStart = BetterDragSort.onDragStart;
+ item.onDrag = BetterDragSort.onDrag;
+ item.onDragEnd = BetterDragSort.onDragEnd;
+ },
+
+ onDragStart : function(nwPosition, sePosition, nwOffset, seOffset) {
+ var items = this.parentNode.getElementsByTagName("li");
+ var minOffset = Coordinates.northwestOffset(items[0], true);
+ var maxOffset = minOffset;
+ for (var i = 0; i < items.length; i++) {
+ maxOffset = maxOffset.max(Coordinates.northwestOffset(items[i], true));
+ }
+ this.constrain(minOffset, maxOffset);
+ },
+
+ onDrag : function(nwPosition, sePosition, nwOffset, seOffset) {
+ var swapper = null;
+
+ var next = DragUtils.nextItem(this);
+ while (next != null) {
+ var nextNWOffset = Coordinates.northwestOffset(next, true);
+ var nextSEOffset = Coordinates.southeastOffset(next, true);
+
+ if (nwOffset.y >= (nextNWOffset.y - 2) &&
+ nwOffset.y <= (nextSEOffset.y + 2) &&
+ nwOffset.x >= (nextNWOffset.x - 2) &&
+ nwOffset.x <= (nextSEOffset.x + 2)) {
+ var swapper = next;
+ break;
+ }
+ var next = DragUtils.nextItem(next);
+ }
+ if (swapper != null) {
+ BetterDragSort.moveAfter(this, swapper);
+ return;
+ }
+
+ var previous = DragUtils.previousItem(this);
+ while (previous != null) {
+ var previousNWOffset = Coordinates.northwestOffset(previous, true);
+ var previousSEOffset = Coordinates.southeastOffset(previous, true);
+
+ var fudgeFactor = 2;
+ if (nwOffset.y >= (previousNWOffset.y - fudgeFactor) &&
+ nwOffset.y <= (previousSEOffset.y + fudgeFactor) &&
+ nwOffset.x >= (previousNWOffset.x - fudgeFactor) &&
+ nwOffset.x <= (previousSEOffset.x + fudgeFactor)) {
+ var swapper = previous;
+ break;
+ }
+ var previous = DragUtils.previousItem(previous);
+ }
+ if (swapper != null) {
+ BetterDragSort.moveBefore(this, swapper);
+ return;
+ }
+ },
+
+ moveAfter : function(item1, item2) {
+ var parent = item1.parentNode;
+ parent.removeChild(item1);
+ parent.insertBefore(item1, item2.nextSibling);
+
+ item1.style["top"] = "0px";
+ item1.style["left"] = "0px";
+ },
+
+ moveBefore : function(item1, item2) {
+ var parent = item1.parentNode;
+ parent.removeChild(item1);
+ parent.insertBefore(item1, item2);
+
+ item1.style["top"] = "0px";
+ item1.style["left"] = "0px";
+ },
+
+ onDragEnd : function(nwPosition, sePosition, nwOffset, seOffset) {
+ this.style["top"] = "0px";
+ this.style["left"] = "0px";
+ }
+};
+
+
+var DragSort = {
+
+ makeListSortable : function(list) {
+ var items = list.getElementsByTagName("li");
+
+ for (var i = 0; i < items.length; i++) {
+ DragSort.makeItemSortable(items[i]);
+ }
+ },
+
+ makeItemSortable : function(item) {
+ Drag.makeDraggable(item);
+ item.setDragThresholdY(5);
+
+ item.onDragStart = DragSort.onDragStart;
+ item.onDrag = DragSort.onDrag;
+ item.onDragEnd = DragSort.onDragEnd;
+ },
+
+ onDragStart : function(nwPosition, sePosition, nwOffset, seOffset) {
+ var items = this.parentNode.getElementsByTagName("li");
+ var minOffset = Coordinates.northwestOffset(items[0], true);
+ var maxOffset = Coordinates.northwestOffset(items[items.length - 1], true);
+ this.constrain(minOffset, maxOffset);
+ },
+
+ onDrag : function(nwPosition, sePosition, nwOffset, seOffset) {
+ var parent = this.parentNode;
+
+ var item = this;
+ var next = DragUtils.nextItem(item);
+ while (next != null && this.offsetTop >= next.offsetTop - 2) {
+ var item = next;
+ var next = DragUtils.nextItem(item);
+ }
+ if (this != item) {
+ DragUtils.swap(this, next);
+ return;
+ }
+
+ var item = this;
+ var previous = DragUtils.previousItem(item);
+ while (previous != null && this.offsetTop <= previous.offsetTop + 2) {
+ var item = previous;
+ var previous = DragUtils.previousItem(item);
+ }
+ if (this != item) {
+ DragUtils.swap(this, item);
+ return;
+ }
+ },
+
+ onDragEnd : function(nwPosition, sePosition, nwOffset, seOffset) {
+ this.style["top"] = "0px";
+ this.style["left"] = "0px";
+ }
+};
+
+var DragSortX = {
+
+ makeListSortable : function(list) {
+ var items = list.getElementsByTagName("li");
+
+ var minOffset = Coordinates.northwestOffset(items[0], true);
+ var maxOffset = Coordinates.northwestOffset(items[items.length - 1], true);
+
+ for (var i = 0; i < items.length; i++) {
+ Drag.makeDraggable(items[i]);
+ items[i].constrain(minOffset, maxOffset);
+ items[i].setDragThresholdX(5);
+
+ items[i].onDrag = DragSortX.onDrag;
+
+ items[i].onDragEnd = function(nwPosition, sePosition, nwOffset, seOffset) {
+ this.style["top"] = "0px";
+ this.style["left"] = "0px";
+ };
+ }
+ },
+
+ onDrag : function(nwPosition, sePosition, nwOffset, seOffset) {
+ var parent = this.parentNode;
+
+ var item = this;
+ var next = DragUtils.nextItem(item);
+ while (next != null && this.offsetLeft >= next.offsetLeft - 2) {
+ var item = next;
+ var next = DragUtils.nextItem(item);
+ }
+ if (this != item) {
+ DragUtils.swap(this, next);
+ return;
+ }
+
+ var item = this;
+ var previous = DragUtils.previousItem(item);
+ while (previous != null && this.offsetLeft <= previous.offsetLeft + 2) {
+ var item = previous;
+ var previous = DragUtils.previousItem(item);
+ }
+ if (this != item) {
+ DragUtils.swap(this, item);
+ return;
+ }
+ }
+};
+
+var DragUtils = {
+ swap : function(item1, item2) {
+ var parent = item1.parentNode;
+ parent.removeChild(item1);
+ parent.insertBefore(item1, item2);
+
+ item1.style["top"] = "0px";
+ item1.style["left"] = "0px";
+ },
+
+ nextItem : function(item) {
+ var sibling = item.nextSibling;
+ while (sibling != null) {
+ if (sibling.nodeName == item.nodeName) return sibling;
+ sibling = sibling.nextSibling;
+ }
+ return null;
+ },
+
+ previousItem : function(item) {
+ var sibling = item.previousSibling;
+ while (sibling != null) {
+ if (sibling.nodeName == item.nodeName) return sibling;
+ sibling = sibling.previousSibling;
+ }
+ return null;
+ }
+};