From 2f674606cfcf3bc405454ee836cc560791659f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98rjan=20Ertkjern?= Date: Thu, 14 Mar 2019 19:37:54 +0100 Subject: [PATCH] V2.0.0b --- package-lock.json | 199 ++++++++++++------ package.json | 1 + src/app/home/history/history.component.html | 19 +- src/app/home/history/history.component.ts | 43 +++- src/app/home/home.component.html | 10 +- src/app/home/home.module.ts | 11 +- .../register-match.component.html | 66 +----- .../register-match.component.scss | 57 +---- .../register-match.component.ts | 69 +----- .../register1vs1-match.component.html | 64 ++++++ .../register1vs1-match.component.scss | 69 ++++++ .../register1vs1-match.component.ts | 72 +++++++ .../register2vs2-match.component.html | 72 +++++++ .../register2vs2-match.component.scss | 70 ++++++ .../register2vs2-match.component.ts | 103 +++++++++ src/app/home/score/score.component.html | 26 +-- src/app/home/score/score.component.scss | 26 +-- src/app/home/score/score.component.ts | 16 +- .../score/score1vs1/score1vs1.component.html | 21 ++ .../score/score1vs1/score1vs1.component.scss | 24 +++ .../score/score1vs1/score1vs1.component.ts | 27 +++ .../score/score2vs2/score2vs2.component.html | 21 ++ .../score/score2vs2/score2vs2.component.scss | 24 +++ .../score/score2vs2/score2vs2.component.ts | 27 +++ .../match-type-selector.component.html | 9 + .../match-type-selector.component.scss | 22 ++ .../match-type-selector.component.ts | 25 +++ .../player-selector.component.html | 5 + .../player-selector.component.scss | 0 .../player-selector.component.ts | 36 ++++ .../shared/models/generic-history.model.ts | 10 + src/app/shared/models/match-type.enum.ts | 4 + src/app/shared/models/team-history.model.ts | 18 ++ src/app/shared/models/team-match.model.ts | 10 + src/app/shared/models/team.model.ts | 11 + src/app/shared/services/history.service.ts | 9 + .../services/matchmaker.service.spec.ts | 126 +++++++++++ src/app/shared/services/matchmaker.service.ts | 119 +++++++++-- src/app/shared/services/team.service.ts | 77 +++++++ src/app/shared/shared.module.ts | 15 +- src/environments/environment.prod.ts | 2 +- src/environments/environment.ts | 2 +- src/styles/mixins.scss | 1 + 43 files changed, 1292 insertions(+), 346 deletions(-) create mode 100644 src/app/home/register-winner/register1vs1-match/register1vs1-match.component.html create mode 100644 src/app/home/register-winner/register1vs1-match/register1vs1-match.component.scss create mode 100644 src/app/home/register-winner/register1vs1-match/register1vs1-match.component.ts create mode 100644 src/app/home/register-winner/register2vs2-match/register2vs2-match.component.html create mode 100644 src/app/home/register-winner/register2vs2-match/register2vs2-match.component.scss create mode 100644 src/app/home/register-winner/register2vs2-match/register2vs2-match.component.ts create mode 100644 src/app/home/score/score1vs1/score1vs1.component.html create mode 100644 src/app/home/score/score1vs1/score1vs1.component.scss create mode 100644 src/app/home/score/score1vs1/score1vs1.component.ts create mode 100644 src/app/home/score/score2vs2/score2vs2.component.html create mode 100644 src/app/home/score/score2vs2/score2vs2.component.scss create mode 100644 src/app/home/score/score2vs2/score2vs2.component.ts create mode 100644 src/app/shared/components/match-type-selector/match-type-selector.component.html create mode 100644 src/app/shared/components/match-type-selector/match-type-selector.component.scss create mode 100644 src/app/shared/components/match-type-selector/match-type-selector.component.ts create mode 100644 src/app/shared/components/player-selector/player-selector.component.html create mode 100644 src/app/shared/components/player-selector/player-selector.component.scss create mode 100644 src/app/shared/components/player-selector/player-selector.component.ts create mode 100644 src/app/shared/models/generic-history.model.ts create mode 100644 src/app/shared/models/match-type.enum.ts create mode 100644 src/app/shared/models/team-history.model.ts create mode 100644 src/app/shared/models/team-match.model.ts create mode 100644 src/app/shared/models/team.model.ts create mode 100644 src/app/shared/services/matchmaker.service.spec.ts create mode 100644 src/app/shared/services/team.service.ts diff --git a/package-lock.json b/package-lock.json index 66ecc64..ddf8678 100644 --- a/package-lock.json +++ b/package-lock.json @@ -482,7 +482,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -503,12 +504,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -523,17 +526,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -650,7 +656,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -662,6 +669,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -676,6 +684,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -683,12 +692,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -707,6 +718,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -787,7 +799,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -799,6 +812,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -884,7 +898,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -920,6 +935,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -939,6 +955,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -982,12 +999,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -999,7 +1018,7 @@ }, "load-json-file": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { @@ -1020,7 +1039,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -1052,7 +1071,7 @@ }, "pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, @@ -1533,7 +1552,7 @@ }, "whatwg-fetch": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "resolved": "http://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" } } @@ -1781,13 +1800,13 @@ }, "@types/node": { "version": "8.9.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz", + "resolved": "http://registry.npmjs.org/@types/node/-/node-8.9.5.tgz", "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==", "dev": true }, "@types/q": { "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "resolved": "http://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", "dev": true }, @@ -2193,6 +2212,7 @@ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, + "optional": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -2430,7 +2450,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -2679,7 +2699,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -2780,7 +2800,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -2817,7 +2837,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -2871,7 +2891,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -3391,7 +3411,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "dev": true, + "optional": true }, "constants-browserify": { "version": "1.0.0", @@ -3515,7 +3536,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -3528,7 +3549,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -3786,7 +3807,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true + "dev": true, + "optional": true }, "depd": { "version": "1.1.2", @@ -3845,7 +3867,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -4527,7 +4549,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "dev": true, "requires": { @@ -4756,7 +4778,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -4777,12 +4800,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4797,17 +4822,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4924,7 +4952,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -4936,6 +4965,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4950,6 +4980,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4957,12 +4988,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -4981,6 +5014,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5061,7 +5095,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5073,6 +5108,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5158,7 +5194,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5194,6 +5231,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5213,6 +5251,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5256,12 +5295,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -5270,6 +5311,7 @@ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", "dev": true, + "optional": true, "requires": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", @@ -5282,6 +5324,7 @@ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, + "optional": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -5319,11 +5362,12 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true + "dev": true, + "optional": true }, "get-stream": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true }, @@ -5929,7 +5973,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true + "dev": true, + "optional": true }, "has-value": { "version": "1.0.0", @@ -6032,7 +6077,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -6081,7 +6126,7 @@ }, "http-proxy-middleware": { "version": "0.18.0", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { @@ -6487,7 +6532,7 @@ }, "is-builtin-module": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { @@ -6651,7 +6696,8 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true + "dev": true, + "optional": true }, "is-windows": { "version": "1.0.2", @@ -7077,7 +7123,7 @@ }, "es6-promise": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", + "resolved": "http://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=", "dev": true }, @@ -7089,7 +7135,7 @@ }, "readable-stream": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "dev": true, "requires": { @@ -7280,9 +7326,10 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, + "optional": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", @@ -7295,7 +7342,8 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "dev": true, + "optional": true } } }, @@ -7597,7 +7645,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "dev": true, + "optional": true }, "map-visit": { "version": "1.0.0", @@ -7667,7 +7716,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true @@ -7778,7 +7827,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, @@ -7868,7 +7917,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -8008,7 +8057,7 @@ "dependencies": { "semver": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "dev": true, "optional": true @@ -8100,7 +8149,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "optional": true, @@ -8225,6 +8274,7 @@ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, + "optional": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -8409,7 +8459,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "requires": { "lcid": "^1.0.0" @@ -9044,7 +9094,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -9086,7 +9136,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -9306,6 +9356,7 @@ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, + "optional": true, "requires": { "load-json-file": "^1.0.0", "normalize-package-data": "^2.3.2", @@ -9317,6 +9368,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, + "optional": true, "requires": { "graceful-fs": "^4.1.2", "pify": "^2.0.0", @@ -9327,7 +9379,8 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "dev": true, + "optional": true } } }, @@ -9336,6 +9389,7 @@ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, + "optional": true, "requires": { "find-up": "^1.0.0", "read-pkg": "^1.0.0" @@ -9346,6 +9400,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, + "optional": true, "requires": { "path-exists": "^2.0.0", "pinkie-promise": "^2.0.0" @@ -9356,6 +9411,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, + "optional": true, "requires": { "pinkie-promise": "^2.0.0" } @@ -9364,7 +9420,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -9707,7 +9763,7 @@ }, "sax": { "version": "0.5.8", - "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "resolved": "http://registry.npmjs.org/sax/-/sax-0.5.8.tgz", "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=", "dev": true }, @@ -9912,7 +9968,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -10607,7 +10663,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" @@ -10618,6 +10674,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, + "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -10925,7 +10982,7 @@ }, "through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -11088,7 +11145,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -11252,6 +11309,11 @@ "unique-slug": "^2.0.0" } }, + "unique-names-generator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-names-generator/-/unique-names-generator-2.0.1.tgz", + "integrity": "sha512-ZtkPNDatgorFwQiaNkQGlXv4pvN3fZM5Sacj5meWPbsWk/dF+wfDj4SNx9ufY17M6eTkK5F8D0JHoYbbihl3fw==" + }, "unique-slug": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.1.tgz", @@ -11963,6 +12025,7 @@ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, + "optional": true, "requires": { "string-width": "^1.0.2 || 2" } @@ -11983,7 +12046,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { "string-width": "^1.0.1", @@ -12026,7 +12089,7 @@ }, "xmlbuilder": { "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", "dev": true }, diff --git a/package.json b/package.json index 368a7d3..eff6aff 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "jquery": "^3.3.1", "rxjs": "^6.4.0", "tslib": "^1.9.0", + "unique-names-generator": "^2.0.1", "zone.js": "~0.8.29" }, "devDependencies": { diff --git a/src/app/home/history/history.component.html b/src/app/home/history/history.component.html index fc7fa7a..9a19a15 100644 --- a/src/app/home/history/history.component.html +++ b/src/app/home/history/history.component.html @@ -1,7 +1,7 @@
-

Recent matches

+

Last 20 matches

@@ -10,26 +10,25 @@

Recent matches

{{h.created |date: 'dd/MM/yyyy hh:mm' }}
-

- + - {{h.player1NewRating - h.player1OldRating}} +

+ + + {{h.winnerNewRating - h.winnerOldRating}}

-

{{h.player1Username}} ({{h.player1NewRating}})

+

{{h.winnerName}}
({{h.winnerNewRating}})

VS

-

{{h.player2Username}} ({{h.player2NewRating}})

+

{{h.loserName}}
({{h.loserNewRating}})

-

- + - {{h.player2NewRating - h.player2OldRating}} +

+ + + {{h.loserNewRating - h.loserOldRating}}

- diff --git a/src/app/home/history/history.component.ts b/src/app/home/history/history.component.ts index d17ad9a..3f9bac4 100644 --- a/src/app/home/history/history.component.ts +++ b/src/app/home/history/history.component.ts @@ -1,6 +1,10 @@ import {Component, OnInit} from '@angular/core'; import {HistoryService} from '../../shared/services/history.service'; import {HistoryModel} from '../../shared/models/history.model'; +import {TeamHistoryModel} from '../../shared/models/team-history.model'; +import {GenericHistoryModel} from '../../shared/models/generic-history.model'; +import {combineLatest} from 'rxjs'; +import {map} from 'rxjs/operators'; @Component({ selector: 'app-history', @@ -9,20 +13,53 @@ import {HistoryModel} from '../../shared/models/history.model'; }) export class HistoryComponent implements OnInit { - history: HistoryModel[]; + history: GenericHistoryModel[]; constructor(private historyService: HistoryService) { } + private static convertUserHistory(history: HistoryModel): GenericHistoryModel { + const tmp = new GenericHistoryModel(); + tmp.created = history.created; + tmp.lastUpdated = history.lastUpdated; + tmp.winnerName = history.player1Name; + tmp.winnerOldRating = history.player1OldRating; + tmp.winnerNewRating = history.player1NewRating; + tmp.loserName = history.player2Name; + tmp.loserOldRating = history.player2OldRating; + tmp.loserNewRating = history.player2NewRating; + return tmp; + } + + private static convertTeamHistory(history: TeamHistoryModel): GenericHistoryModel { + const tmp = new GenericHistoryModel(); + tmp.created = history.created; + tmp.lastUpdated = history.lastUpdated; + tmp.winnerName = history.team1Name; + tmp.winnerOldRating = history.team1OldRating; + tmp.winnerNewRating = history.team1NewRating; + tmp.loserName = history.team2Name; + tmp.loserOldRating = history.team2OldRating; + tmp.loserNewRating = history.team2NewRating; + return tmp; + } + ngOnInit() { this.loadHistory(); } loadHistory() { - this.historyService.getHistory(10).subscribe(result => { - this.history = result; + combineLatest(this.historyService.getHistory(20), this.historyService.getTeamHistory(20)).pipe( + map(([userHistory, teamHistory]) => { + return userHistory.map(h => HistoryComponent.convertUserHistory(h)) + .concat(teamHistory.map(h => HistoryComponent.convertTeamHistory(h))); + }), + ).subscribe(history => { + history.sort((a, b) => b.created - a.created); + this.history = history.splice(0, 20); }, error => { console.log(error); }); } + } diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index ce44cc3..a3254c8 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -24,14 +24,14 @@
-

Release 1.2.1 Beta

+

Release 2.0.0 Beta

This release has the following changes:

diff --git a/src/app/home/home.module.ts b/src/app/home/home.module.ts index 8702f8e..801ec65 100644 --- a/src/app/home/home.module.ts +++ b/src/app/home/home.module.ts @@ -12,6 +12,10 @@ import {HistoryComponent} from './history/history.component'; import {RouterModule} from '@angular/router'; import {RegisterMatchComponent} from './register-winner/register-match.component'; import {NgSelectModule} from '@ng-select/ng-select'; +import { Register1vs1MatchComponent } from './register-winner/register1vs1-match/register1vs1-match.component'; +import { Register2vs2MatchComponent } from './register-winner/register2vs2-match/register2vs2-match.component'; +import { Score1vs1Component } from './score/score1vs1/score1vs1.component'; +import { Score2vs2Component } from './score/score2vs2/score2vs2.component'; @NgModule({ @@ -32,7 +36,12 @@ import {NgSelectModule} from '@ng-select/ng-select'; ScoreComponent, GamesComponent, RegisterMatchComponent, - HistoryComponent], + HistoryComponent, + Register1vs1MatchComponent, + Register2vs2MatchComponent, + Score1vs1Component, + Score2vs2Component, + ], providers: [], }) export class HomeModule { diff --git a/src/app/home/register-winner/register-match.component.html b/src/app/home/register-winner/register-match.component.html index 1af25cf..7abe0a5 100644 --- a/src/app/home/register-winner/register-match.component.html +++ b/src/app/home/register-winner/register-match.component.html @@ -1,63 +1,5 @@ -
-
-
-

Register a match

-
-
-
-
- - - Velg... - {{user.username}} ({{user.name}}) - -
-
won against
-
- - - Velg... - {{user.username}} ({{user.name}}) - -
-
- -
-
- -
-
-
-
-

🏆 {{player1.name}} WON! 🏆

-
-
-

- + - {{matchResult.player1NewRating - matchResult.player1OldRating}} -

-
-
-

{{matchResult.player1Username}} ({{matchResult.player1NewRating}})

-
-
-

{{matchResult.player2Username}} ({{matchResult.player2NewRating}})

-
-
-

- + - {{matchResult.player2NewRating - matchResult.player2OldRating}} -

-
-
-
-
- -
-
-
-
-
- - +
+ + +
diff --git a/src/app/home/register-winner/register-match.component.scss b/src/app/home/register-winner/register-match.component.scss index cf7d65f..a386e82 100644 --- a/src/app/home/register-winner/register-match.component.scss +++ b/src/app/home/register-winner/register-match.component.scss @@ -1,55 +1,4 @@ -@import "../../../styles/variables"; -@import "../../../styles/mixins"; - -.register-match{ - margin-top: 35px; - text-align: center; - - .select-label{ - text-align: left; - font-size: 18px; - } - .vs{ - display: block; - text-align: center; - margin-top: 24px; - font-size: 28px - } - .select-box{ - margin: auto; - width: 100%; - font-size: 18px; - border: none; - background: none; - border: 1px solid $tangarine; - border-radius: 0px; - background-color: white; - display: block; - } - - .create-game{ - @include no-button-styling(); - @include standard-button($tangarine, $tangarine-transparent, white); - width: 300px; - font-size: 22px; - padding: 5px; - margin-top: 35px; - } - - .loss{ - color: red; - } - - .win{ - color: green; - } - - .header-winner{ - margin-top: 35px; - } - - .summary-match{ - font-size: 22px; - margin-top: 35px; - } +.container { + display: flex; + flex-direction: column; } diff --git a/src/app/home/register-winner/register-match.component.ts b/src/app/home/register-winner/register-match.component.ts index 79432f5..6ee67cc 100644 --- a/src/app/home/register-winner/register-match.component.ts +++ b/src/app/home/register-winner/register-match.component.ts @@ -1,10 +1,5 @@ import {Component, OnInit} from '@angular/core'; -import {UserService} from '../../shared/services/user.service'; -import {AuthenticationService} from '../../shared/services/authentication.service'; -import {MatchmakerService} from '../../shared/services/matchmaker.service'; -import {UserModel} from '../../shared/models/user.model'; -import {MatchModel} from '../../shared/models/match.model'; -import {HistoryModel} from '../../shared/models/history.model'; +import {MatchType} from '../../shared/models/match-type.enum'; @Component({ selector: 'app-register-match', @@ -12,67 +7,17 @@ import {HistoryModel} from '../../shared/models/history.model'; styleUrls: ['register-match.component.scss'] }) export class RegisterMatchComponent implements OnInit { - users: UserModel[]; - player1: UserModel; - player2: UserModel; - matchResult: HistoryModel; - player1Id: string; - player2Id: string; - isLoading: boolean; - hasRegisterd: boolean; + matchTypes = MatchType; - constructor(private userService: UserService, private auth: AuthenticationService, private matchmaker: MatchmakerService) { - } - - ngOnInit() { - this.player1Id = ''; - this.player2Id = ''; - this.auth.isLoggedIn().subscribe(result => { - this.loadUsers(result.uid); - }, error => { - console.log(error); - }); - } + display: string; - private loadUsers(id: string) { - this.userService.getUsersByName().subscribe(result => { - this.users = result; - }, error => { - console.log(error); - }); + constructor() { } - registerWinner() { - if (this.player2Id && this.player1Id) { - this.player1 = this.users.filter(u => u.uid === this.player1Id)[0]; - if (confirm('Did ' + this.player1.name + ' win?')) { - this.isLoading = true; - this.player2 = this.users.filter(u => u.uid === this.player2Id)[0]; - const matchModel: MatchModel = this.createMatch(this.player1, this.player2); - this.matchmaker.registerWinner(true, matchModel).then(result => { - this.isLoading = false; - if (result) { - this.matchResult = result; - this.hasRegisterd = true; - } - }); - } else { - console.log('Cancel'); - } - } - } - - registerNewMatch() { - this.hasRegisterd = false; + ngOnInit() { } - private createMatch(player1: UserModel, player2): MatchModel { - const match: MatchModel = { - player1: player1, - player2: player2, - lastUpdated: new Date(), - created: new Date().getTime() - }; - return match; + select(type) { + this.display = type; } } diff --git a/src/app/home/register-winner/register1vs1-match/register1vs1-match.component.html b/src/app/home/register-winner/register1vs1-match/register1vs1-match.component.html new file mode 100644 index 0000000..4b54b04 --- /dev/null +++ b/src/app/home/register-winner/register1vs1-match/register1vs1-match.component.html @@ -0,0 +1,64 @@ +
+
+
+

Register a 1 vs 1 match

+
+
+
+ +
+ +
+
+
+ + +
+
won against
+
+ + +
+
+ +
+
+ +
+
+
+
+

🏆 {{player1.name}} WON! 🏆

+
+
+

+ + + {{matchResult.player1NewRating - matchResult.player1OldRating}} +

+
+
+

{{matchResult.player1Username}} ({{matchResult.player1NewRating}})

+
+
+

{{matchResult.player2Username}} ({{matchResult.player2NewRating}})

+
+
+

+ + + {{matchResult.player2NewRating - matchResult.player2OldRating}} +

+
+
+
+
+ +
+
+
+
+
+ + +
diff --git a/src/app/home/register-winner/register1vs1-match/register1vs1-match.component.scss b/src/app/home/register-winner/register1vs1-match/register1vs1-match.component.scss new file mode 100644 index 0000000..f7acedd --- /dev/null +++ b/src/app/home/register-winner/register1vs1-match/register1vs1-match.component.scss @@ -0,0 +1,69 @@ +@import "../../../../styles/variables"; +@import "../../../../styles/mixins"; + +.register-match { + margin-top: 35px; + text-align: center; + + app-player-selector { + width: 100%; + } + + .selector-row { + > div { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + .vs { + display: block; + text-align: center; + font-size: 28px + } + } + + .select-label { + text-align: left; + font-size: 18px; + } + + .select-box { + margin: auto; + width: 100%; + font-size: 18px; + border: none; + background: none; + border: 1px solid $tangarine; + border-radius: 0px; + background-color: white; + display: block; + } + + .create-game { + @include no-button-styling(); + @include standard-button($tangarine, $tangarine-transparent, white); + width: 300px; + font-size: 22px; + padding: 5px; + margin-top: 35px; + } + + .loss { + color: red; + } + + .win { + color: green; + } + + .header-winner { + margin-top: 35px; + } + + .summary-match { + font-size: 22px; + margin-top: 35px; + } +} diff --git a/src/app/home/register-winner/register1vs1-match/register1vs1-match.component.ts b/src/app/home/register-winner/register1vs1-match/register1vs1-match.component.ts new file mode 100644 index 0000000..94069e2 --- /dev/null +++ b/src/app/home/register-winner/register1vs1-match/register1vs1-match.component.ts @@ -0,0 +1,72 @@ +import {Component, OnInit} from '@angular/core'; +import {UserModel} from '../../../shared/models/user.model'; +import {HistoryModel} from '../../../shared/models/history.model'; +import {UserService} from '../../../shared/services/user.service'; +import {AuthenticationService} from '../../../shared/services/authentication.service'; +import {MatchmakerService} from '../../../shared/services/matchmaker.service'; +import {MatchModel} from '../../../shared/models/match.model'; + +@Component({ + selector: 'app-register1vs1-match', + templateUrl: './register1vs1-match.component.html', + styleUrls: ['./register1vs1-match.component.scss'] +}) +export class Register1vs1MatchComponent implements OnInit { + users: UserModel[]; + player1: UserModel; + player2: UserModel; + matchResult: HistoryModel; + isLoading: boolean; + hasRegisterd: boolean; + + constructor(private userService: UserService, private auth: AuthenticationService, private matchmaker: MatchmakerService) { + } + + ngOnInit() { + this.auth.isLoggedIn().subscribe(result => { + this.loadUsers(result.uid); + }, error => { + console.log(error); + }); + } + + private loadUsers(id: string) { + this.userService.getUsersByName().subscribe(result => { + this.users = result; + }, error => { + console.log(error); + }); + } + + registerWinner() { + if (this.player1 && this.player2) { + if (confirm('Did ' + this.player1.name + ' win?')) { + this.isLoading = true; + const matchModel: MatchModel = this.createMatch(this.player1, this.player2); + this.matchmaker.registerWinner(true, matchModel).then(result => { + this.isLoading = false; + if (result) { + this.matchResult = result; + this.hasRegisterd = true; + } + }); + } else { + console.log('Cancel'); + } + } + } + + registerNewMatch() { + this.hasRegisterd = false; + } + + private createMatch(player1: UserModel, player2): MatchModel { + const match: MatchModel = { + player1: player1, + player2: player2, + lastUpdated: new Date(), + created: new Date().getTime() + }; + return match; + } +} diff --git a/src/app/home/register-winner/register2vs2-match/register2vs2-match.component.html b/src/app/home/register-winner/register2vs2-match/register2vs2-match.component.html new file mode 100644 index 0000000..872f234 --- /dev/null +++ b/src/app/home/register-winner/register2vs2-match/register2vs2-match.component.html @@ -0,0 +1,72 @@ +
+
+
+

Register a 2 vs 2 match

+
+
+
+ +
+ +
+
+
+

Winners

+ + + + +
+
won against
+
+

Losers

+ + + + +
+
+ +
+
+ +
+
+
+
+

🏆 {{matchResult.team1Name}} WON! 🏆

+
+
+

+ + + {{matchResult.team1NewRating - matchResult.team1OldRating}} +

+
+
+

{{matchResult.team1Name}} ({{matchResult.team1NewRating}})

+
+
+

{{matchResult.team2Name}} ({{matchResult.team2NewRating}})

+
+
+

+ + + {{matchResult.team2NewRating - matchResult.team2OldRating}} +

+
+
+
+
+ +
+
+
+
+
+
diff --git a/src/app/home/register-winner/register2vs2-match/register2vs2-match.component.scss b/src/app/home/register-winner/register2vs2-match/register2vs2-match.component.scss new file mode 100644 index 0000000..aa15207 --- /dev/null +++ b/src/app/home/register-winner/register2vs2-match/register2vs2-match.component.scss @@ -0,0 +1,70 @@ +@import "../../../../styles/variables"; +@import "../../../../styles/mixins"; + +.register-match { + margin-top: 35px; + text-align: center; + + app-player-selector { + width: 100%; + } + + + .selector-row { + > div { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + .vs { + display: block; + text-align: center; + font-size: 28px + } + } + + .select-label { + text-align: left; + font-size: 18px; + } + + .select-box { + margin: auto; + width: 100%; + font-size: 18px; + border: none; + background: none; + border: 1px solid $tangarine; + border-radius: 0px; + background-color: white; + display: block; + } + + .create-game { + @include no-button-styling(); + @include standard-button($tangarine, $tangarine-transparent, white); + width: 300px; + font-size: 22px; + padding: 5px; + margin-top: 35px; + } + + .loss { + color: red; + } + + .win { + color: green; + } + + .header-winner { + margin-top: 35px; + } + + .summary-match { + font-size: 22px; + margin-top: 35px; + } +} diff --git a/src/app/home/register-winner/register2vs2-match/register2vs2-match.component.ts b/src/app/home/register-winner/register2vs2-match/register2vs2-match.component.ts new file mode 100644 index 0000000..f86ddc2 --- /dev/null +++ b/src/app/home/register-winner/register2vs2-match/register2vs2-match.component.ts @@ -0,0 +1,103 @@ +import {Component, OnInit} from '@angular/core'; +import {AuthenticationService} from '../../../shared/services/authentication.service'; +import {MatchmakerService} from '../../../shared/services/matchmaker.service'; +import {TeamService} from '../../../shared/services/team.service'; +import {TeamModel} from '../../../shared/models/team.model'; +import {TeamHistoryModel} from '../../../shared/models/team-history.model'; +import {filter, switchMap} from 'rxjs/operators'; +import {TeamMatchModel} from '../../../shared/models/team-match.model'; +import {UserModel} from '../../../shared/models/user.model'; +import {UserService} from '../../../shared/services/user.service'; +import {uniqueNamesGenerator} from 'unique-names-generator'; +import {combineLatest, Observable} from 'rxjs'; + +@Component({ + selector: 'app-register2vs2-match', + templateUrl: './register2vs2-match.component.html', + styleUrls: ['./register2vs2-match.component.scss'] +}) +export class Register2vs2MatchComponent implements OnInit { + users: UserModel[]; + players: UserModel[] = new Array(4); + teams: TeamModel[]; + matchResult: TeamHistoryModel; + isLoading: boolean; + hasRegistered: boolean; + + constructor(private userService: UserService, + private teamService: TeamService, + private auth: AuthenticationService, + private matchmaker: MatchmakerService, + ) { + } + + private static createMatch(team1: TeamModel, team2: TeamModel): TeamMatchModel { + return { + team1: team1, + team2: team2, + lastUpdated: new Date(), + created: new Date().getTime() + } as TeamMatchModel; + } + + ngOnInit() { + this.loadUsers(); + } + + private loadUsers() { + this.userService.getUsersByName().subscribe(result => { + this.users = result; + }, error => { + console.log(error); + }); + } + + private allPlayersHaveValue() { + return this.players.length === 4 && this.players.every(p => p !== null && typeof p !== 'undefined'); + } + + playersWithout(player: UserModel): UserModel[] { + return this.players.filter(p => p !== player); + } + + registerWinner() { + if (!this.allPlayersHaveValue()) { + return; + } else if (!confirm('Did ' + this.players[0].name + ' & ' + this.players[1].name + ' win?')) { + console.log('Cancel'); + return; + } + this.isLoading = true; + + const team1$ = this.getOrRegisterTeam(this.players[0], this.players[1]); + const team2$ = this.getOrRegisterTeam(this.players[2], this.players[3]); + combineLatest(team1$, team2$).pipe( + switchMap(([team1, team2]) => { + const match = Register2vs2MatchComponent.createMatch(team1, team2); + return this.matchmaker.registerWinnerTeam(true, match); + }) + ).subscribe(result => { + this.isLoading = false; + if (result) { + this.matchResult = result; + this.hasRegistered = true; + } + }); + } + + getOrRegisterTeam(player1: UserModel, player2: UserModel): Observable { + return this.teamService.teamExists(player1.uid, player2.uid).pipe( + switchMap(exists => { + if (exists) { + return this.teamService.getTeam(player1.uid, player2.uid); + } else { + return this.teamService.register(player1.uid, player1.name, player2.uid, player2.name, uniqueNamesGenerator()); + } + }), + ); + } + + registerNewMatch() { + this.hasRegistered = false; + } +} diff --git a/src/app/home/score/score.component.html b/src/app/home/score/score.component.html index 899d250..9779d5b 100644 --- a/src/app/home/score/score.component.html +++ b/src/app/home/score/score.component.html @@ -1,21 +1,5 @@ -
-

Leaderboard

-
-
-
-
-
- {{i+1}}. -
-
- {{user.username}} -
-
- {{user.rating}} -
-
-
-
-
- -
+
+ + + +
diff --git a/src/app/home/score/score.component.scss b/src/app/home/score/score.component.scss index f9f7499..a386e82 100644 --- a/src/app/home/score/score.component.scss +++ b/src/app/home/score/score.component.scss @@ -1,24 +1,4 @@ -@import "../../../styles/variables"; -@import "../../../styles/mixins"; - -.score { - margin-top: 35px; - h2{ - font-size: 32px; - } - text-align: center; - .score-content { - margin-top: 35px; - text-align: left; - width: 100%; - - .user{ - font-size: 18px; - padding: 5px 5px 5px 5px; - border-bottom: 1px solid $tangarine; - } - } - @media screen and (max-width: $large-screen) { - width: 100%; - } +.container { + display: flex; + flex-direction: column; } diff --git a/src/app/home/score/score.component.ts b/src/app/home/score/score.component.ts index f14ae07..70cfa0e 100644 --- a/src/app/home/score/score.component.ts +++ b/src/app/home/score/score.component.ts @@ -1,6 +1,7 @@ import {Component, OnInit} from '@angular/core'; import {UserService} from '../../shared/services/user.service'; import {UserModel} from '../../shared/models/user.model'; +import {MatchType} from '../../shared/models/match-type.enum'; @Component({ selector: 'app-score', @@ -8,20 +9,17 @@ import {UserModel} from '../../shared/models/user.model'; templateUrl: 'score.component.html' }) export class ScoreComponent implements OnInit { - users: UserModel[]; + matchTypes = MatchType; - constructor(private userService: UserService) { + display: string; + + constructor() { } ngOnInit() { - this.loadScores(); } - private loadScores() { - this.userService.getUsersByScore().subscribe(result => { - this.users = result; - }, error => { - console.log('Something went wrong: ' + error); - }); + select(type) { + this.display = type; } } diff --git a/src/app/home/score/score1vs1/score1vs1.component.html b/src/app/home/score/score1vs1/score1vs1.component.html new file mode 100644 index 0000000..aac2db3 --- /dev/null +++ b/src/app/home/score/score1vs1/score1vs1.component.html @@ -0,0 +1,21 @@ +
+

1 vs 1 Leaderboard

+
+
+
+
+
+ {{i+1}}. +
+
+ {{user.username}} +
+
+ {{user.rating}} +
+
+
+
+
+ +
diff --git a/src/app/home/score/score1vs1/score1vs1.component.scss b/src/app/home/score/score1vs1/score1vs1.component.scss new file mode 100644 index 0000000..68ba187 --- /dev/null +++ b/src/app/home/score/score1vs1/score1vs1.component.scss @@ -0,0 +1,24 @@ +@import "../../../../styles/variables"; +@import "../../../../styles/mixins"; + +.score { + margin-top: 35px; + h2{ + font-size: 32px; + } + text-align: center; + .score-content { + margin-top: 35px; + text-align: left; + width: 100%; + + .user{ + font-size: 18px; + padding: 5px 5px 5px 5px; + border-bottom: 1px solid $tangarine; + } + } + @media screen and (max-width: $large-screen) { + width: 100%; + } +} diff --git a/src/app/home/score/score1vs1/score1vs1.component.ts b/src/app/home/score/score1vs1/score1vs1.component.ts new file mode 100644 index 0000000..d773754 --- /dev/null +++ b/src/app/home/score/score1vs1/score1vs1.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from '@angular/core'; +import {UserModel} from '../../../shared/models/user.model'; +import {UserService} from '../../../shared/services/user.service'; + +@Component({ + selector: 'app-score1vs1', + templateUrl: './score1vs1.component.html', + styleUrls: ['./score1vs1.component.scss'] +}) +export class Score1vs1Component implements OnInit { + users: UserModel[]; + + constructor(private userService: UserService) { + } + + ngOnInit() { + this.loadScores(); + } + + private loadScores() { + this.userService.getUsersByScore().subscribe(result => { + this.users = result; + }, error => { + console.log('Something went wrong: ' + error); + }); + } +} diff --git a/src/app/home/score/score2vs2/score2vs2.component.html b/src/app/home/score/score2vs2/score2vs2.component.html new file mode 100644 index 0000000..ed7388e --- /dev/null +++ b/src/app/home/score/score2vs2/score2vs2.component.html @@ -0,0 +1,21 @@ +
+

2 vs 2 Leaderboard

+
+
+
+
+
+ {{i+1}}. +
+
+ {{team.name}} ({{team.player1Name}} - {{team.player2Name}}) +
+
+ {{team.rating}} +
+
+
+
+
+ +
diff --git a/src/app/home/score/score2vs2/score2vs2.component.scss b/src/app/home/score/score2vs2/score2vs2.component.scss new file mode 100644 index 0000000..3f9b81b --- /dev/null +++ b/src/app/home/score/score2vs2/score2vs2.component.scss @@ -0,0 +1,24 @@ +@import "../../../../styles/variables"; +@import "../../../../styles/mixins"; + +.score { + margin-top: 35px; + h2{ + font-size: 32px; + } + text-align: center; + .score-content { + margin-top: 35px; + text-align: left; + width: 100%; + + .team{ + font-size: 18px; + padding: 5px 5px 5px 5px; + border-bottom: 1px solid $tangarine; + } + } + @media screen and (max-width: $large-screen) { + width: 100%; + } +} diff --git a/src/app/home/score/score2vs2/score2vs2.component.ts b/src/app/home/score/score2vs2/score2vs2.component.ts new file mode 100644 index 0000000..6baf5c9 --- /dev/null +++ b/src/app/home/score/score2vs2/score2vs2.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from '@angular/core'; +import {TeamModel} from '../../../shared/models/team.model'; +import {TeamService} from '../../../shared/services/team.service'; + +@Component({ + selector: 'app-score2vs2', + templateUrl: './score2vs2.component.html', + styleUrls: ['./score2vs2.component.scss'] +}) +export class Score2vs2Component implements OnInit { + teams: TeamModel[]; + + constructor(private teamService: TeamService) { + } + + ngOnInit() { + this.loadScores(); + } + + private loadScores() { + this.teamService.getTeamsByScore().subscribe(result => { + this.teams = result; + }, error => { + console.log('Something went wrong: ' + error); + }); + } +} diff --git a/src/app/shared/components/match-type-selector/match-type-selector.component.html b/src/app/shared/components/match-type-selector/match-type-selector.component.html new file mode 100644 index 0000000..fe73cfe --- /dev/null +++ b/src/app/shared/components/match-type-selector/match-type-selector.component.html @@ -0,0 +1,9 @@ +
+ +
+ +
diff --git a/src/app/shared/components/match-type-selector/match-type-selector.component.scss b/src/app/shared/components/match-type-selector/match-type-selector.component.scss new file mode 100644 index 0000000..bd7eff5 --- /dev/null +++ b/src/app/shared/components/match-type-selector/match-type-selector.component.scss @@ -0,0 +1,22 @@ +@import "../../../../styles/variables"; + +.match-types { + margin-top: 35px; + display: flex; +} + +.type { + background: $tangarine-transparent; + border: 0; + border-radius: 4px; + color: #fff; + flex-grow: 1; + + &.selected { + background: $tangarine; + } +} + +.seperator { + width: 25%; +} diff --git a/src/app/shared/components/match-type-selector/match-type-selector.component.ts b/src/app/shared/components/match-type-selector/match-type-selector.component.ts new file mode 100644 index 0000000..2a798b4 --- /dev/null +++ b/src/app/shared/components/match-type-selector/match-type-selector.component.ts @@ -0,0 +1,25 @@ +import {Component, EventEmitter, OnInit, Output} from '@angular/core'; +import {MatchType} from '../../models/match-type.enum'; + +@Component({ + selector: 'app-match-type-selector', + templateUrl: './match-type-selector.component.html', + styleUrls: ['./match-type-selector.component.scss'] +}) +export class MatchTypeSelectorComponent implements OnInit { + + @Output() selected = new EventEmitter(); + _selected: MatchType = MatchType.ONE_VS_ONE; + matchType = MatchType; + + constructor() { + } + + ngOnInit() { + } + + select(type: MatchType) { + this._selected = type; + this.selected.emit(this._selected); + } +} diff --git a/src/app/shared/components/player-selector/player-selector.component.html b/src/app/shared/components/player-selector/player-selector.component.html new file mode 100644 index 0000000..0558a4b --- /dev/null +++ b/src/app/shared/components/player-selector/player-selector.component.html @@ -0,0 +1,5 @@ + + + Velg... + {{player.name}} ({{player.username}}) + diff --git a/src/app/shared/components/player-selector/player-selector.component.scss b/src/app/shared/components/player-selector/player-selector.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shared/components/player-selector/player-selector.component.ts b/src/app/shared/components/player-selector/player-selector.component.ts new file mode 100644 index 0000000..205e335 --- /dev/null +++ b/src/app/shared/components/player-selector/player-selector.component.ts @@ -0,0 +1,36 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {UserModel} from '../../models/user.model'; + +@Component({ + selector: 'app-player-selector', + templateUrl: './player-selector.component.html', + styleUrls: ['./player-selector.component.scss'] +}) +export class PlayerSelectorComponent implements OnInit { + + _player: UserModel; + @Output() playerChange = new EventEmitter(); + @Input() players: UserModel[]; + @Input() disabled: UserModel[]; + @Input() label: string; + @Input() hideLabel: boolean; + @Input() get player() { + return this._player; + } + + constructor() { + } + + isDisabled(player: UserModel) { + return this.disabled.indexOf(player) !== -1; + } + + ngOnInit() { + } + + set player(val) { + this._player = val; + this.playerChange.emit(this._player); + } + +} diff --git a/src/app/shared/models/generic-history.model.ts b/src/app/shared/models/generic-history.model.ts new file mode 100644 index 0000000..fc6faf5 --- /dev/null +++ b/src/app/shared/models/generic-history.model.ts @@ -0,0 +1,10 @@ +export class GenericHistoryModel { + created: any; + lastUpdated: any; + winnerName: string; + winnerOldRating: number; + winnerNewRating: number; + loserName: string; + loserOldRating: number; + loserNewRating: number; +} diff --git a/src/app/shared/models/match-type.enum.ts b/src/app/shared/models/match-type.enum.ts new file mode 100644 index 0000000..f26e67a --- /dev/null +++ b/src/app/shared/models/match-type.enum.ts @@ -0,0 +1,4 @@ +export enum MatchType { + ONE_VS_ONE = '1vs1', + TWO_VS_TWO = '2vs2', +} diff --git a/src/app/shared/models/team-history.model.ts b/src/app/shared/models/team-history.model.ts new file mode 100644 index 0000000..c68d982 --- /dev/null +++ b/src/app/shared/models/team-history.model.ts @@ -0,0 +1,18 @@ +export class TeamHistoryModel { + created: any; + lastUpdated: any; + team1Uid: string + team1Name: string; + team1Player1Name: string; + team1Player2Name: string; + team1OldRating: number; + team1NewRating: number; + team2Uid: string; + team2Name: string; + team2Player1Name: string; + team2Player2Name: string; + team2OldRating: number; + team2NewRating: number; + team1Won: boolean; + +} diff --git a/src/app/shared/models/team-match.model.ts b/src/app/shared/models/team-match.model.ts new file mode 100644 index 0000000..2f9037a --- /dev/null +++ b/src/app/shared/models/team-match.model.ts @@ -0,0 +1,10 @@ +import {TeamModel} from './team.model'; + +export class TeamMatchModel { + id?: string; + created: any; + lastUpdated: any; + team1: TeamModel; + team2: TeamModel; + winner?: TeamModel; +} diff --git a/src/app/shared/models/team.model.ts b/src/app/shared/models/team.model.ts new file mode 100644 index 0000000..9b5609c --- /dev/null +++ b/src/app/shared/models/team.model.ts @@ -0,0 +1,11 @@ + +export class TeamModel { + uid?: string; + isPlaying: boolean; + player1Uid: string; + player1Name: string; + player2Uid: string; + player2Name: string; + name: string; + rating: number; +} diff --git a/src/app/shared/services/history.service.ts b/src/app/shared/services/history.service.ts index 94d2a7b..b89ddff 100644 --- a/src/app/shared/services/history.service.ts +++ b/src/app/shared/services/history.service.ts @@ -2,11 +2,13 @@ import {Injectable} from '@angular/core'; import {AngularFirestore, AngularFirestoreCollection} from '@angular/fire/firestore'; import {HistoryModel} from '../models/history.model'; import {Observable} from 'rxjs'; +import {TeamHistoryModel} from '../models/team-history.model'; @Injectable() export class HistoryService { private historyCollection: AngularFirestoreCollection; + private teamHistoryCollection: AngularFirestoreCollection; constructor(private afs: AngularFirestore) { } @@ -17,4 +19,11 @@ export class HistoryService { }); return this.historyCollection.valueChanges(); } + + getTeamHistory(top: number): Observable { + this.teamHistoryCollection = this.afs.collection('team-history', ref => { + return ref.orderBy('created', 'desc').limit(top); + }); + return this.teamHistoryCollection.valueChanges(); + } } diff --git a/src/app/shared/services/matchmaker.service.spec.ts b/src/app/shared/services/matchmaker.service.spec.ts new file mode 100644 index 0000000..c80ca0d --- /dev/null +++ b/src/app/shared/services/matchmaker.service.spec.ts @@ -0,0 +1,126 @@ +import {TestBed} from '@angular/core/testing'; + +import {MatchmakerService} from './matchmaker.service'; +import {MatchModel} from '../models/match.model'; +import {UserModel} from '../models/user.model'; +import {AngularFirestore} from '@angular/fire/firestore'; +import {TeamMatchModel} from '../models/team-match.model'; +import {TeamModel} from '../models/team.model'; + +const AngularFirestoreStub = { + createId: () => 'createId', + collection: (name: string) => ({ + valueChanges: () => 'valueChanges', + snapshotChanges: () => 'snapshotChanges', + doc: (_id: string) => ({ + valueChanges: () => 'valuChanges', + set: (_d: any) => 'set', + }), + }), +}; + +const setup1v1Match = (player1Elo, player2Elo): MatchModel => { + const match = new MatchModel(); + match.player1 = userWithRating(player1Elo); + match.player2 = userWithRating(player2Elo); + return match; +}; + +const setup2v2Match = (team1Rating, team2Rating): TeamMatchModel => { + const match = new TeamMatchModel(); + match.team1 = new TeamModel(); + match.team1.rating = team1Rating; + match.team2 = new TeamModel(); + match.team2.rating = team2Rating; + return match; +}; + +const userWithRating = (rating): UserModel => { + const user = new UserModel(); + user.rating = rating; + return user; +}; + +describe('MatchmakerService', () => { + beforeEach(() => TestBed.configureTestingModule({ + providers: [ + MatchmakerService, + {provide: AngularFirestore, useValue: AngularFirestoreStub}, + ] + }).compileComponents() + ); + + it('should be created', () => { + const service = TestBed.get(MatchmakerService); + expect(service).toBeTruthy(); + }); + + describe('calculate1v1ELORating', () => { + it('should result in player1(1510), player2(1490)', () => { + const service = TestBed.get(MatchmakerService); + const match = setup1v1Match(1500, 1500); + const actual = service['calculate1v1ELORating'](true, match); + expect(actual.player1.rating).toEqual(1510); + expect(actual.player2.rating).toEqual(1490); + }); + + it('should result in player1(1490), player2(1510)', () => { + const service = TestBed.get(MatchmakerService); + const match = setup1v1Match(1500, 1500); + const actual = service['calculate1v1ELORating'](false, match); + expect(actual.player1.rating).toEqual(1490); + expect(actual.player2.rating).toEqual(1510); + }); + + it('should result in player1(1000), player2(2000)', () => { + const service = TestBed.get(MatchmakerService); + const match = setup1v1Match(1000, 2000); + const actual = service['calculate1v1ELORating'](true, match); + expect(actual.player1.rating).toEqual(1020); + expect(actual.player2.rating).toEqual(1980); + }); + + it('should result in player1(1374), player2(1520)', () => { + const service = TestBed.get(MatchmakerService); + const match = setup1v1Match(1360, 1520); + const actual = service['calculate1v1ELORating'](true, match); + expect(actual.player1.rating).toEqual(1374); + expect(actual.player2.rating).toEqual(1506); + }); + + it('should result in player1(1481), player2(1648)', () => { + const service = TestBed.get(MatchmakerService); + const match = setup1v1Match(1487, 1642); + const actual = service['calculate1v1ELORating'](false, match); + expect(actual.player1.rating).toEqual(1481); + expect(actual.player2.rating).toEqual(1648); + }); + }); + + describe('calculate2v2ELORating', () => { + it('should result in player1(1510), player2(1490)', () => { + const service = TestBed.get(MatchmakerService); + const match = setup2v2Match(1500, 1500); + const actual = service['calculate2v2ELORating'](true, match); + expect(actual.team1.rating).toEqual(1510); + expect(actual.team2.rating).toEqual(1490); + }); + + + it('should result in player1(1490), player2(1510)', () => { + const service = TestBed.get(MatchmakerService); + const match = setup2v2Match(1500, 1500); + const actual = service['calculate2v2ELORating'](false, match); + expect(actual.team1.rating).toEqual(1490); + expect(actual.team2.rating).toEqual(1510); + }); + + it('should result in player1(1000), player2(2000)', () => { + const service = TestBed.get(MatchmakerService); + const match = setup2v2Match( 1000, 2000); + const actual = service['calculate2v2ELORating'](true, match); + expect(actual.team1.rating).toEqual(1020); + expect(actual.team2.rating).toEqual(1980); + }); + }); +}); diff --git a/src/app/shared/services/matchmaker.service.ts b/src/app/shared/services/matchmaker.service.ts index 261a5f5..2abdb13 100644 --- a/src/app/shared/services/matchmaker.service.ts +++ b/src/app/shared/services/matchmaker.service.ts @@ -4,6 +4,9 @@ import {AngularFirestore, AngularFirestoreCollection} from '@angular/fire/firest import {MatchModel} from '../models/match.model'; import {map} from 'rxjs/operators'; import {HistoryModel} from '../models/history.model'; +import {TeamMatchModel} from '../models/team-match.model'; +import {TeamHistoryModel} from '../models/team-history.model'; +import {TeamModel} from '../models/team.model'; @Injectable() export class MatchmakerService { @@ -21,7 +24,7 @@ export class MatchmakerService { lastUpdated: new Date(), created: new Date().getTime() }; - this.afs.collection('matches').add(match).then( () => { + this.afs.collection('matches').add(match).then(() => { this.setIsPlaying(player1, true).then(() => { this.setIsPlaying(player2, true).then(() => { resolve(true); @@ -43,16 +46,16 @@ export class MatchmakerService { return ref.orderBy('created', 'asc'); }); return this.matchCollection.snapshotChanges().pipe( - map(actions => actions.map(a => { + map(actions => actions.map(a => { const data = a.payload.doc.data() as MatchModel; const id = a.payload.doc.id; - return {id, ...data }; + return {id, ...data}; })) ); } deleteMatch(match: MatchModel): Promise { - return new Promise(resolve => { + return new Promise(resolve => { this.setIsPlaying(match.player1, false).then(() => { this.setIsPlaying(match.player2, false).then(() => { this.afs.collection('matches').doc(match.id).delete().then(() => { @@ -64,10 +67,10 @@ export class MatchmakerService { } registerWinner(player1Won: boolean, match: MatchModel): Promise { - return new Promise( resolve => { + return new Promise(resolve => { const player1OldRating = match.player1.rating; const player2OldRating = match.player2.rating; - const updatedMatch = this.calculateELORating(player1Won, match); + const updatedMatch = this.calculate1v1ELORating(player1Won, match); if (updatedMatch) { this.updatePlayerRating(updatedMatch.player1).then(() => { this.updatePlayerRating(updatedMatch.player2).then(() => { @@ -82,11 +85,37 @@ export class MatchmakerService { }); } + // TODO fix + registerWinnerTeam(team1Won: boolean, match: TeamMatchModel): Promise { + return new Promise(resolve => { + const team1OldRating = match.team1.rating; + const team2OldRating = match.team2.rating; + const updatedMatch = this.calculate2v2ELORating(team1Won, match); + if (updatedMatch) { + this.updateTeamRating(updatedMatch.team1).then(() => { + this.updateTeamRating(updatedMatch.team2).then(() => { + this.updateTeamMatch(match, team1OldRating, team2OldRating, team1Won) + .then(result => { + resolve(result); + }); + }); + }); + } else { + throw new Error('ELO-rating update failed'); + } + }); + } + private updatePlayerRating(player: UserModel): Promise { player.isPlaying = false; return this.afs.collection('users').doc(player.uid).update(player); } + private updateTeamRating(team: TeamModel): Promise { + team.isPlaying = false; + return this.afs.collection('teams').doc(team.uid).update(team); + } + private updateMatch(match: MatchModel, player1Oldrating: number, player2OldRating: number, player1Won): Promise { return new Promise(resolve => { match.lastUpdated = new Date(); @@ -109,19 +138,71 @@ export class MatchmakerService { resolve(normalizedMatch); }); }); + } + private updateTeamMatch(match: TeamMatchModel, + team1OldRating: number, + team2OldRating: number, + team1Won: boolean): Promise { + return new Promise(resolve => { + match.lastUpdated = new Date(); + const normalizedMatch: TeamHistoryModel = { + created: match.created, + lastUpdated: new Date(), + team1Uid: match.team1.uid, + team1Name: match.team1.name, + team1Player1Name: match.team1.player1Name, + team1Player2Name: match.team1.player2Name, + team1OldRating: team1OldRating, + team1NewRating: match.team1.rating, + team2Uid: match.team2.uid, + team2Name: match.team2.name, + team2Player1Name: match.team2.player1Name, + team2Player2Name: match.team2.player2Name, + team2OldRating: team2OldRating, + team2NewRating: match.team2.rating, + team1Won: team1Won + }; + this.afs.collection('team-history').add(normalizedMatch).then(() => { + resolve(normalizedMatch); + }); + }); } - private calculateELORating(player1Won: boolean, match: MatchModel): MatchModel { - let winnerRating = 0; - let loserRating = 0; + private calculate1v1ELORating(player1Won: boolean, match: MatchModel): MatchModel { + if (match.player1.rating <= 0 || match.player2.rating <= 0) { + return null; + } if (player1Won) { - winnerRating = match.player1.rating; - loserRating = match.player2.rating; + const newRatings = this.calculateELORating(match.player1.rating, match.player2.rating); + match.player1.rating = newRatings[0]; + match.player2.rating = newRatings[1]; + } else { + const newRatings = this.calculateELORating(match.player2.rating, match.player1.rating); + match.player1.rating = newRatings[1]; + match.player2.rating = newRatings[0]; + } + return match; + } + + + private calculate2v2ELORating(team1Won: boolean, match: TeamMatchModel): TeamMatchModel { + if (match.team1.rating <= 0 || match.team2.rating <= 0) { + return null; + } + if (team1Won) { + const newRatings = this.calculateELORating(match.team1.rating, match.team2.rating); + match.team1.rating = newRatings[0]; + match.team2.rating = newRatings[1]; } else { - winnerRating = match.player2.rating; - loserRating = match.player1.rating; + const newRatings = this.calculateELORating(match.team2.rating, match.team1.rating); + match.team1.rating = newRatings[1]; + match.team2.rating = newRatings[0]; } + return match; + } + + private calculateELORating(winnerRating: number, loserRating: number): [number, number] { if (winnerRating > 0 && loserRating > 0) { const rw = Math.pow(10, (winnerRating / 400)); const rl = Math.pow(10, (loserRating / 400)); @@ -132,16 +213,8 @@ export class MatchmakerService { const eloWinner = winnerRating + 20 * (1 - e1); const eloloser = loserRating + 20 * (0 - e2); - if (player1Won) { - match.player1.rating = Math.round(eloWinner); - match.player2.rating = Math.round(eloloser); - } else { - match.player1.rating = Math.round(eloloser); - match.player2.rating = Math.round(eloWinner); - } - return match; + return [Math.round(eloWinner), Math.round(eloloser)]; } - return null; - + return [0, 0]; } } diff --git a/src/app/shared/services/team.service.ts b/src/app/shared/services/team.service.ts new file mode 100644 index 0000000..4c49c2f --- /dev/null +++ b/src/app/shared/services/team.service.ts @@ -0,0 +1,77 @@ +import {Injectable} from '@angular/core'; +import {AngularFirestore, AngularFirestoreCollection} from '@angular/fire/firestore'; +import {TeamModel} from '../models/team.model'; +import {combineLatest, Observable, zip} from 'rxjs'; +import {fromPromise} from 'rxjs/internal-compatibility'; +import {map, switchMap, take} from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class TeamService { + + private teamCollection: AngularFirestoreCollection; + + constructor(private afs: AngularFirestore) { + } + + getTeamsByName(): Observable { + this.teamCollection = this.afs.collection('teams', ref => { + return ref.orderBy('name', 'asc'); + }); + return this.teamCollection.valueChanges(); + } + + getTeamsByScore(): Observable { + this.teamCollection = this.afs.collection('teams', ref => { + return ref.orderBy('rating', 'desc'); + }); + return this.teamCollection.valueChanges(); + } + + + getTeam(player1Uid: string, player2Uid: string): Observable { + const o1$ = this.afs.collection('teams', ref => + ref.where('player1Uid', '==', player1Uid).where('player2Uid', '==', player2Uid)); + + const o2$ = this.afs.collection('teams', ref => + ref.where('player2Uid', '==', player1Uid).where('player1Uid', '==', player2Uid)); + + return zip(o1$.valueChanges(), o2$.valueChanges()).pipe( + map(([a1, a2]) => a1.concat(a2)[0]) + ); + } + + teamExists(player1Uid: string, player2Uid: string): Observable { + const o1$ = this.afs.collection('teams', ref => + ref.where('player1Uid', '==', player1Uid).where('player2Uid', '==', player2Uid)); + + const o2$ = this.afs.collection('teams', ref => + ref.where('player2Uid', '==', player1Uid).where('player1Uid', '==', player2Uid)); + + return combineLatest(o1$.get(), o2$.get()).pipe( + map(([o1, o2]) => o1.size > 0 || o2.size > 0), + ); + } + + register(player1Uid: string, player1Name: string, player2Uid: string, player2Name: string, name: string): Observable { + const team = { + isPlaying: false, + player1Uid: player1Uid, + player1Name: player1Name, + player2Uid: player2Uid, + player2Name: player2Name, + name: name, + rating: 1500, + } as TeamModel; + return fromPromise(this.afs.collection('teams').add(team)).pipe( + switchMap(docRef => + fromPromise(this.afs.collection('teams').doc(docRef.id).update({uid: docRef.id})).pipe( + map(() => docRef.id), + ) + ), + switchMap(id => this.afs.collection('teams').doc(id).valueChanges().pipe(take(1))), + ); + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 4f3cf2a..41aa0b0 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -7,12 +7,21 @@ import {CommonModule} from '@angular/common'; import {UserService} from './services/user.service'; import {MatchmakerService} from './services/matchmaker.service'; import {HistoryService} from './services/history.service'; +import {MatchTypeSelectorComponent} from './components/match-type-selector/match-type-selector.component'; +import {PlayerSelectorComponent} from './components/player-selector/player-selector.component'; +import {NgSelectModule} from '@ng-select/ng-select'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; @NgModule({ - imports: [CommonModule], - exports: [HeaderComponent], - declarations: [HeaderComponent], + imports: [ + CommonModule, + FormsModule, + NgSelectModule, + ReactiveFormsModule, + ], + exports: [HeaderComponent, MatchTypeSelectorComponent, PlayerSelectorComponent], + declarations: [HeaderComponent, MatchTypeSelectorComponent, PlayerSelectorComponent], providers: [ValidationService, AuthenticationService, AuthGuard, UserService, MatchmakerService, HistoryService], }) export class SharedModule { diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 038f1d3..317c69c 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,6 +1,6 @@ export const environment = { production: true, - version: '1.1.1b', + version: '2.0.0b', firebase: { apiKey: '', authDomain: '', diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 038f1d3..317c69c 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,6 +1,6 @@ export const environment = { production: true, - version: '1.1.1b', + version: '2.0.0b', firebase: { apiKey: '', authDomain: '', diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss index e5887ec..945c36a 100644 --- a/src/styles/mixins.scss +++ b/src/styles/mixins.scss @@ -69,3 +69,4 @@ border: 1px solid $hover-color; } } +