diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4da0039 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# Stage 1: Build the Angular application +FROM node:20 as build + +# Set the working directory +WORKDIR /app + +# Copy package.json and package-lock.json to install dependencies +COPY package*.json ./ + +# Install Node.js dependencies +RUN npm install + +# Copy the rest of the application code +COPY . . + +# Build the Angular application in production mode +RUN npm run build --configuration=prod + +# Stage 2: Serve the app with Nginx +FROM nginx:1.21-alpine + +# Copy the built app from the previous stage +COPY --from=build /app/dist/thanksgiving /usr/share/nginx/html + +# Copy the custom Nginx configuration +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Expose port 80 +EXPOSE 80 + +# Start Nginx server +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/gift.json b/gift.json deleted file mode 100644 index b259c43..0000000 --- a/gift.json +++ /dev/null @@ -1,201 +0,0 @@ -[{ -"prizeID": 1, -"name": "Черные носки похуиста", -"isGifted": false -}, -{ -"prizeID": 2, -"name": "Красные носки с алфавитом (выучи эти буквы, наконец!)", -"isGifted": false -}, -{ -"prizeID": 3, -"name": "Червячков, кислых как твои щи", -"isGifted": false -}, -{ -"prizeID": 4, -"name": "Массажер для жопы", -"isGifted": false -}, -{ -"prizeID": 5, -"name": "Мыльные пузыри от Жозе", -"isGifted": false -}, -{ -"prizeID": 6, -"name": "Татуировки переводные \"Вечер в хату\"", -"isGifted": false -}, -{ -"prizeID": 7, -"name": "Шары воздушные женские", -"isGifted": false -}, -{ -"prizeID": 8, -"name": "Неоновые палочки моргалочки", -"isGifted": false -}, -{ -"prizeID": 9, -"name": "Свечу в гранулах с ароматом собачьих жоп", -"isGifted": false -}, -{ -"prizeID": 10, -"name": "Чучело белки омерзительное", -"isGifted": false -}, -{ -"prizeID": 11, -"name": "Почтовый ящик с пингвином", -"isGifted": false -}, -{ -"prizeID": 12, -"name": "Ленту новогоднюю нарядную", -"isGifted": false -}, -{ -"prizeID": 13, -"name": "Резинку для волос от Тимошенко", -"isGifted": false -}, -{ -"prizeID": 14, -"name": "Воздушный шар в виде пингвина", -"isGifted": false -}, -{ -"prizeID": 15, -"name": "Мармеладки из медвежатины", -"isGifted": false -}, -{ -"prizeID": 16, -"name": "Мыльные пузыри космические", -"isGifted": false -}, -{ -"prizeID": 17, -"name": "Ободок деда Мороза красный прекрасный", -"isGifted": false -}, -{ -"prizeID": 18, -"name": "Палочки с ароматом тайского блядюшника", -"isGifted": false -}, -{ -"prizeID": 19, -"name": "Набор для выживания", -"isGifted": false -}, -{ -"prizeID": 20, -"name": "Ободок зеленый, как ты после грузинских маршруток", -"isGifted": false -}, -{ -"prizeID": 21, -"name": "Наклейки новогодние", -"isGifted": false -}, -{ -"prizeID": 22, -"name": "Книгу джунглей", -"isGifted": false -}, -{ -"prizeID": 23, -"name": "Брелок патриотический", -"isGifted": false -}, -{ -"prizeID": 24, -"name": "Солевого Георгия", -"isGifted": false -}, -{ -"prizeID": 25, -"name": "Глину полимерную синюю, как ты после чачи", -"isGifted": false -}, -{ -"prizeID": 26, -"name": "Листы для заметок весёленькие", -"isGifted": false -}, -{ -"prizeID": 27, -"name": "Дракончика серого, как жизнь без вина", -"isGifted": false -}, -{ -"prizeID": 28, -"name": "Паззл-предсказание твоего светлого будущего", -"isGifted": false -}, -{ -"prizeID": 29, -"name": "Глину с ароматом глины (набор)", -"isGifted": false -}, -{ -"prizeID": 30, -"name": "Носки от Лукашенко", -"isGifted": false -}, -{ -"prizeID": 31, -"name": "Лося-летчика", -"isGifted": false -}, -{ -"prizeID": 32, -"name": "Щетку для массажа простаты", -"isGifted": false -}, -{ -"prizeID": 33, -"name": "Тесто для лепки хачапури (ведро)", -"isGifted": false -}, -{ -"prizeID": 34, -"name": "Карусель на выборах", -"isGifted": false -}, -{ -"prizeID": 35, -"name": "Зеркало компактное \"Котик\" (зеркало котик, а не ты)", -"isGifted": false -}, -{ -"prizeID": 36, -"name": "Дракончика нетрадиционного розового", -"isGifted": false -}, -{ -"prizeID": 37, -"name": "Глину полимерную желтую несмешную", -"isGifted": false -}, -{ -"prizeID": 38, -"name": "Ручку с алмазом, как у Путина", -"isGifted": false -}, -{ -"prizeID": 39, -"name": "Ручку зеленую с цыпленком", -"isGifted": false -}, -{ -"prizeID": 40, -"name": "Светильник романтишный", -"isGifted": false -} -] \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..fcd2c37 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,11 @@ +server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e858c06..2653efe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "i": "^0.3.7", "jquery": "^3.6.0", "npm": "^10.2.3", - "rxjs": "~6.6.0", + "rxjs": "^7.8.1", "socket.io-client": "^4.2.0", "tslib": "^2.3.0", "zone.js": "~0.13.3" @@ -34,7 +34,7 @@ "@angular/cli": "^16.2.9", "@angular/compiler-cli": "~16.2.12", "@types/jasmine": "~3.8.0", - "@types/node": "^12.11.1", + "@types/node": "^12.20.55", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -71,15 +71,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/architect/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/@angular-devkit/build-angular": { "version": "16.2.9", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.9.tgz", @@ -241,15 +232,6 @@ "vite": "^3.0.0 || ^4.0.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/vite": { "version": "4.4.7", "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz", @@ -324,15 +306,6 @@ "webpack-dev-server": "^4.0.0" } }, - "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/@angular-devkit/core": { "version": "16.2.9", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.9.tgz", @@ -360,15 +333,6 @@ } } }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/@angular-devkit/schematics": { "version": "16.2.9", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.9.tgz", @@ -387,15 +351,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/schematics/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/@angular/animations": { "version": "16.2.12", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.12.tgz", @@ -3691,7 +3646,8 @@ "version": "12.20.55", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node-forge": { "version": "1.3.9", @@ -7331,15 +7287,6 @@ "node": ">=8" } }, - "node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/inquirer/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -13424,21 +13371,14 @@ } }, "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" + "tslib": "^2.1.0" } }, - "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -15627,17 +15567,6 @@ "requires": { "@angular-devkit/core": "16.2.9", "rxjs": "7.8.1" - }, - "dependencies": { - "rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - } } }, "@angular-devkit/build-angular": { @@ -15738,15 +15667,6 @@ "dev": true, "requires": {} }, - "rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, "vite": { "version": "4.4.7", "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz", @@ -15769,17 +15689,6 @@ "requires": { "@angular-devkit/architect": "0.1602.9", "rxjs": "7.8.1" - }, - "dependencies": { - "rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - } } }, "@angular-devkit/core": { @@ -15794,17 +15703,6 @@ "picomatch": "2.3.1", "rxjs": "7.8.1", "source-map": "0.7.4" - }, - "dependencies": { - "rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - } } }, "@angular-devkit/schematics": { @@ -15818,17 +15716,6 @@ "magic-string": "0.30.1", "ora": "5.4.1", "rxjs": "7.8.1" - }, - "dependencies": { - "rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - } } }, "@angular/animations": { @@ -20871,15 +20758,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -25090,18 +24968,11 @@ } }, "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "requires": { - "tslib": "^1.9.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } + "tslib": "^2.1.0" } }, "safe-buffer": { diff --git a/package.json b/package.json index afafcf0..d06d966 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "i": "^0.3.7", "jquery": "^3.6.0", "npm": "^10.2.3", - "rxjs": "~6.6.0", + "rxjs": "^7.8.1", "socket.io-client": "^4.2.0", "tslib": "^2.3.0", "zone.js": "~0.13.3" @@ -36,7 +36,7 @@ "@angular/cli": "^16.2.9", "@angular/compiler-cli": "~16.2.12", "@types/jasmine": "~3.8.0", - "@types/node": "^12.11.1", + "@types/node": "^12.20.55", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/punishments.json b/punishments.json index ee322cd..2193aba 100644 --- a/punishments.json +++ b/punishments.json @@ -1,170 +1,152 @@ [ { - "text": "Расскажите про свою самую любимую игрушку" + "text": "Сделать смешной комплимент каждому присутствующему" }, { - "text": "Назовите 20 слов на букву Ч" + "text": "Назвать 20 слов на букву Ч" }, { - "text": "Без слов изобразите то, чем приходится заниматься на работе, чтобы присутствующие угадали." + "text": "Рассказать две истории - правду и выдумку. Остальные должны угадать, где какая" }, { - "text": "Изобразите 5 видов спорта так, чтобы присутствующие смогли их назвать." + "text": "Сосед ручкой рисует тебе татуировку на руке" }, { - "text": "Выполните приседания (10 раз), положив на голову книгу." + "text": "Пусть кто-то из игроков сделает тебе новую прическу" }, { - "text": "Посчитайте любую считалку, на ком она остановится, должен выпить с тобой" + "text": "Сделать себе патчи под глаза из огурца и сидеть так 5 минут" }, { - "text": "Попросите каждого игрока по очереди назвать слово и придумать к нему рифму" + "text": "Стоя дотянуться коленкой до носа" }, { - "text": "Назовите 5 грузинских вин" + "text": "Нарисовать себе усы" }, { - "text": "Изобразите кота, которому страшно, но любопытно " + "text": "В течение минуты гладить себя одновременно по голове и по животу, но в разных направлениях" }, { - "text": "Сделайте необычный подарок игроку с максимальным количеством очков, не выходя из комнаты" + "text": "Прочитать любое детское стихотворение с кавказским акцентом" }, { - "text": "Нарисуйте или приклейте милые усики " + "text": "Сказать быстро 3 скороговорки" }, { - "text": "Изобразите иностранца. Говорите на любом языке, можно даже на собственном." + "text": "Играть пантомиму \"Гопник\": сесть на корточки и щелкать семечки, можно потребовать мелочь у ближайших игроков" }, { - "text": "Расскажите плохой анекдот" + "text": "Набрать в рот орехов и произнести 5 раз фразу \"толстощекий вкусноежка\"" }, { - "text": "Опишите свою работу тремя словами" + "text": "Съесть четверть лимона" }, { - "text": "Распознайте на ощупь 5 разных предметов с завязанными глазами, конечно же." + "text": "Подпрыгивая на одной ноге, читать стихотворение" }, { - "text": "Подпрыгните 10 раз, каждый раз произнося \"индейка\"." + "text": "Попрыгать по комнате как лягушка" }, { - "text": "Расскажите стих, в котором будет ваше имя." + "text": "Выпить вино из блюдца без помощи рук" }, { - "text": "Изобразите свой любимый фрукт без слов." + "text": "Написать свое имя на бумаге, держа карандаш в зубах" }, { - "text": "Расскажите мини-историю о забавных приключениях вашей левой руки." + "text": "Открыть книгу на любой странице и прочитать отрывок похотливым голосом" }, { - "text": "Придумайте себе псевдоним и откликайтесь только на него следующие 5 минут." + "text": "Попытаться засунуть в рот кулак" }, { - "text": "Изобразите муравья, который нашел огромную еду." + "text": "Выполнить приседания (10 раз), положив на голову книгу" }, { - "text": "Нарисуйте свой знак зодиака, чтобы остальные игроки отгадали." + "text": "Посчитать любую считалку, на ком она остановится, должен выпить с тобой" }, { - "text": "Подпевайте любимой песне, заменяя слова на \"ля-ля-ля\"." + "text": "Рассказать плохой анекдот" }, { - "text": "Играйте в невидимую гитару и исполняйте короткую мелодию." + "text": "Распознать на ощупь 5 разных предметов (с завязанными глазами, конечно же)" }, { - "text": "Представьте, что вы робот, и произнесите что-то с использованием роботизированного голоса." + "text": "Подпрыгнуть 10 раз, каждый раз произнося \"индейка\"" }, { - "text": "Изобразите свой страх перед любым предметом в комнате." + "text": "Изобразить муравья, который нашел огромную еду" }, { - "text": "Постарайтесь нарисовать свою любимую песню." + "text": "Представить, что вы все находитесь на красной дорожке, и ты - главная звезда. Пройтись по комнате с гордой осанкой" }, { - "text": "Изобразите смешное животное, которого нет в реальном мире." + "text": "Говорить как пират в течение пяти минут" }, { - "text": "Перевоплотитесь в своего любимого персонажа книги или фильма и представьтесь." + "text": "Дотянуться языком до носа" }, { - "text": "Назовите алфавит задом наперед." + "text": "Изображать известного мультяшного персонажа в течение 1 минуты" }, { - "text": "Спойте отрывок из любимой детской песни." + "text": "Съесть кусочек хлеба, на который несколько игроков добавят один ингредиент по выбору" }, { - "text": "Изобразите, что вы танцуете на льду, не поднимаясь с места." + "text": "Сфотографироваться в самой нелепой позе" }, { - "text": "Постарайтесь сказать \"индейка\" наоборот." + "text": "Попытаться постоять на одной ноге 30 секунд с завязанными глазами" }, { - "text": "Расскажите короткую историю, используя только по три слова в каждом предложении." + "text": "Исполнить короткий танец на месте" }, { - "text": "Играйте в 'испорченный телефон': прошепчите любую фразу первому человеку, а затем посмотрите, как она изменится по цепочке." + "text": "Произнести алфавит, чередуя громкий голос и шёпот" }, { - "text": "Назовите пять стран, начинающихся на букву \"И\"." + "text": "Сказать фразу из трёх слов, которая заставит всех засмеяться" }, { - "text": "Переведите любое слово на вымышленный язык и объясните его значение." + "text": "Танцевать под песню, которую выберут другие" }, { "text": "Представьте, что вы новый супергерой с уникальной способностью, и расскажите о ней." }, { - "text": "Издайте звук, который в вашем представлении соответствует слову 'веселье'." + "text": "Нарисовать кошку, не отрывая ручку от бумаги" }, { - "text": "Расскажите короткую историю о приключениях своей тапочки." + "text": "Изобразить известного супергероя, но на пенсии" }, { - "text": "Изобразите смешное лицо и попросите остальных угадать эмоцию." + "text": "Придумать и показать три способа необычного использования стула" }, { - "text": "Придумайте по одному положительному качества для каждого игрока." + "text": "Изобразить, как вы идёте по раскалённым углям" }, { - "text": "Представьте, что вы ведущий радиошоу и сделайте короткую передачу на любую тему." + "text": "Сказать комплимент каждому игроку, используя слова, начинающиеся на одну букву" }, { - "text": "Постарайтесь сделать звуковое подражание своего любимого животного." + "text": "Придумать стихотворение из 4 строк про самого себя. Рассказать с выражением" }, { - "text": "Расскажите короткую историю о приключениях своего домашнего растения." + "text": "Провести импровизированный урок танцев, обучая всех \"новому движению\"" }, { "text": "Представьтесь как профессиональный критик и дайте короткий обзор своего дня." }, { - "text": "Представьте, что вы находитесь на красной дорожке, и вы - главная звезда. Пройдитесь по комнате с гордой осанкой." + "text": "Сказать скороговорку, зажав язык зубами" }, { - "text": "Играйте в \"замедленное движение\": выполните простую задачу (например, открытие двери) медленно и торжественно." + "text": "Сделать из салфеток или бумаги шляпу и носить её до следующего наказания" }, { - "text": "Говорите как пират в течение пяти минут." + "text": "\"атака роботов\": исполнить наказание, придуманное искусственным интеллектом в реальном времени" }, { "text": "Возьмите на себя роль человеческой статуи и замрите в забавной позе на пять минут." -}, -{ - "text": "Рассказать скороговорку без запинок, если запнулся, то начать заново." -}, -{ - "text": "Нарисовать монобровь." -}, -{ - "text": "Выпить или съесть что-то, не используя руки." -}, -{ - "text": "Дотянуться языком до носа." -}, -{ - "text": "Вылакать стаканчик сока или молока из блюдца." -}, -{ - "text": "Набить рот чем-то вкусненьким и произнести 5 раз фразу \"толстощекий вкуснооежка\"." - } +} ] \ No newline at end of file diff --git a/src/app.constants.ts b/src/app.constants.ts deleted file mode 100644 index 22ea441..0000000 --- a/src/app.constants.ts +++ /dev/null @@ -1,4 +0,0 @@ - export const API_URL = 'http://127.0.0.1:3000'; -//export const WEBSOCK_URL = 'http://127.0.0.1:3000'; -// export const API_URL = 'https://thanksgiving2023.ngweb.io/api'; - export const WEBSOCK_URL = "https://thanksgiving2023.ngweb.io/" diff --git a/src/app/admin/admin-main/admin-main.component.html b/src/app/admin/admin-main/admin-main.component.html new file mode 100644 index 0000000..8ef7813 --- /dev/null +++ b/src/app/admin/admin-main/admin-main.component.html @@ -0,0 +1,6 @@ +
+

Game state

+ + +
+ diff --git a/src/app/admin/admin-main/admin-main.component.scss b/src/app/admin/admin-main/admin-main.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/admin/admin-main/admin-main.component.spec.ts b/src/app/admin/admin-main/admin-main.component.spec.ts new file mode 100644 index 0000000..04075ad --- /dev/null +++ b/src/app/admin/admin-main/admin-main.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminMainComponent } from './admin-main.component'; + +describe('AdminMainComponent', () => { + let component: AdminMainComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [AdminMainComponent] + }); + fixture = TestBed.createComponent(AdminMainComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin-main/admin-main.component.ts b/src/app/admin/admin-main/admin-main.component.ts new file mode 100644 index 0000000..719d3cc --- /dev/null +++ b/src/app/admin/admin-main/admin-main.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-admin-main', + templateUrl: './admin-main.component.html', + styleUrls: ['./admin-main.component.scss'] +}) +export class AdminMainComponent { + +} diff --git a/src/app/admin/admin-routing.module.ts b/src/app/admin/admin-routing.module.ts index 261ed5a..598a087 100644 --- a/src/app/admin/admin-routing.module.ts +++ b/src/app/admin/admin-routing.module.ts @@ -2,6 +2,9 @@ import { NgModule } from "@angular/core"; import { ActivatedRouteSnapshot, RouterModule, RouterStateSnapshot, Routes, UrlTree } from "@angular/router"; import { HomeComponent } from "./home/home.component"; import { Observable, of } from "rxjs"; +import {ConfigurationComponent} from "./configuration/configuration.component"; +import {AdminMainComponent} from "./admin-main/admin-main.component"; +import {AdminTestingComponent} from "./admin-testing/admin-testing.component"; export class AdminGuard { @@ -10,18 +13,38 @@ export class AdminGuard { } canDeactivate(component: HomeComponent, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { + if(nextState?.url.indexOf('admin') !== -1){ + return of(true); + } return of(false); } } - const routes: Routes = [ { path: '', component: HomeComponent, canDeactivate: [AdminGuard], - } + children: [ + { + path:'', + component: AdminMainComponent, + canDeactivate: [AdminGuard], + }, + { + path: 'configuration', + component: ConfigurationComponent, + canDeactivate: [AdminGuard], + }, + { + path:'testing', + component: AdminTestingComponent, + canDeactivate: [AdminGuard], + } + ] + }, + ] @NgModule({ diff --git a/src/app/admin/admin-testing/admin-testing.component.html b/src/app/admin/admin-testing/admin-testing.component.html new file mode 100644 index 0000000..0075a22 --- /dev/null +++ b/src/app/admin/admin-testing/admin-testing.component.html @@ -0,0 +1,20 @@ +
+

Game testing menu

+

Players

+ +

Game

+ + + +

Versus

+ + + + +
+ +
+
+ You are in prod mode, testing disabled +
+
\ No newline at end of file diff --git a/src/app/admin/admin-testing/admin-testing.component.scss b/src/app/admin/admin-testing/admin-testing.component.scss new file mode 100644 index 0000000..57fb940 --- /dev/null +++ b/src/app/admin/admin-testing/admin-testing.component.scss @@ -0,0 +1,6 @@ +div { + + button { + margin: 5px; + } +} \ No newline at end of file diff --git a/src/app/admin/admin-testing/admin-testing.component.spec.ts b/src/app/admin/admin-testing/admin-testing.component.spec.ts new file mode 100644 index 0000000..d3e3612 --- /dev/null +++ b/src/app/admin/admin-testing/admin-testing.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminTestingComponent } from './admin-testing.component'; + +describe('AdminTestingComponent', () => { + let component: AdminTestingComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [AdminTestingComponent] + }); + fixture = TestBed.createComponent(AdminTestingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin-testing/admin-testing.component.ts b/src/app/admin/admin-testing/admin-testing.component.ts new file mode 100644 index 0000000..dcec8de --- /dev/null +++ b/src/app/admin/admin-testing/admin-testing.component.ts @@ -0,0 +1,60 @@ +import {Component, OnDestroy, OnInit} from '@angular/core'; +import {ApiService} from "../../services/api.service"; +import {Subject} from "rxjs"; +import {takeUntil} from "rxjs/operators"; +import {EventService} from "../../services/event.service"; +import {TestingApiService} from "../../services/testing-api.service"; + +@Component({ + selector: 'app-admin-testing', + templateUrl: './admin-testing.component.html', + styleUrls: ['./admin-testing.component.scss'] +}) +export class AdminTestingComponent implements OnInit, OnDestroy { + prodMode = false; + destroyed$ = new Subject(); + constructor( + private apiService: ApiService, + private eventService: EventService, + private testingApiService: TestingApiService) { + } + + ngOnInit(): void { + this.getFFState(); + this.eventService.featureFlagChanged.pipe(takeUntil(this.destroyed$)).subscribe((r) => this.getFFState()); + } + + private getFFState() { + this.apiService.getFeatureFlagState("ProdMode").pipe(takeUntil(this.destroyed$)).subscribe((res) => + { + this.prodMode = res.state; + }); + } + ngOnDestroy() { + this.destroyed$.complete(); + } + + simulateVersus() { + this.testingApiService.simulateVersus().pipe(takeUntil(this.destroyed$)).subscribe((r) => console.log(r)); + } + + resetAllVersusTasksAsIncompleted() { + this.testingApiService.resetAllVersusTasksAsIncompleted().pipe(takeUntil(this.destroyed$)).subscribe((r) => console.log(r)); + } + + resetAllPlayersScore() { + this.testingApiService.resetAllPlayersScore().pipe(takeUntil(this.destroyed$)).subscribe(r => console.log(r)); + } + + clearGameQueue() { + this.testingApiService.clearGameQueue().pipe(takeUntil(this.destroyed$)).subscribe((r => console.log(r))); + } + + simulateEndGamePoints() { + this.testingApiService.simulateEndGamePoints().pipe(takeUntil(this.destroyed$)).subscribe(r => console.log(r)); + } + + simulateValidAnswer() { + this.testingApiService.simulateValidAnswer().pipe(takeUntil(this.destroyed$)).subscribe(r => console.log(r)); + } +} diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts index b3b35a4..8798690 100644 --- a/src/app/admin/admin.module.ts +++ b/src/app/admin/admin.module.ts @@ -3,9 +3,13 @@ import { CommonModule } from '@angular/common'; import { HomeComponent } from './home/home.component'; import { AdminRoutingModule } from "./admin-routing.module"; import { MainActionsComponent } from './components/main-actions/main-actions.component'; -import { AppModule } from "../app.module"; import { SharedModule } from "../shared/shared.module"; import { QueueActionsComponent } from './components/queue-actions/queue-actions.component'; +import { ConfigurationComponent } from './configuration/configuration.component'; +import { AdminNavComponent } from './components/admin-nav/admin-nav.component'; +import { AdminMainComponent } from './admin-main/admin-main.component'; +import { FeatureflagsComponent } from './components/featureflags/featureflags.component'; +import { AdminTestingComponent } from './admin-testing/admin-testing.component'; @@ -13,7 +17,12 @@ import { QueueActionsComponent } from './components/queue-actions/queue-actions. declarations: [ HomeComponent, MainActionsComponent, - QueueActionsComponent + QueueActionsComponent, + ConfigurationComponent, + AdminNavComponent, + AdminMainComponent, + FeatureflagsComponent, + AdminTestingComponent, ], imports: [ CommonModule, AdminRoutingModule, SharedModule, diff --git a/src/app/admin/components/admin-nav/admin-nav.component.html b/src/app/admin/components/admin-nav/admin-nav.component.html new file mode 100644 index 0000000..1b34e8a --- /dev/null +++ b/src/app/admin/components/admin-nav/admin-nav.component.html @@ -0,0 +1,4 @@ + +Main +Testing +Config diff --git a/src/app/admin/components/admin-nav/admin-nav.component.scss b/src/app/admin/components/admin-nav/admin-nav.component.scss new file mode 100644 index 0000000..cf1a774 --- /dev/null +++ b/src/app/admin/components/admin-nav/admin-nav.component.scss @@ -0,0 +1,4 @@ +a:link, a:active, a:visited { + color: white; + padding: 3px; +} diff --git a/src/app/admin/components/admin-nav/admin-nav.component.spec.ts b/src/app/admin/components/admin-nav/admin-nav.component.spec.ts new file mode 100644 index 0000000..9c03deb --- /dev/null +++ b/src/app/admin/components/admin-nav/admin-nav.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminNavComponent } from './admin-nav.component'; + +describe('AdminNavComponent', () => { + let component: AdminNavComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [AdminNavComponent] + }); + fixture = TestBed.createComponent(AdminNavComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/components/admin-nav/admin-nav.component.ts b/src/app/admin/components/admin-nav/admin-nav.component.ts new file mode 100644 index 0000000..3b237d2 --- /dev/null +++ b/src/app/admin/components/admin-nav/admin-nav.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-admin-nav', + templateUrl: './admin-nav.component.html', + styleUrls: ['./admin-nav.component.scss'] +}) +export class AdminNavComponent { + +} diff --git a/src/app/admin/components/featureflags/featureflags.component.html b/src/app/admin/components/featureflags/featureflags.component.html new file mode 100644 index 0000000..59b6e70 --- /dev/null +++ b/src/app/admin/components/featureflags/featureflags.component.html @@ -0,0 +1,7 @@ +
+
+ + +
+ +
\ No newline at end of file diff --git a/src/app/admin/components/featureflags/featureflags.component.scss b/src/app/admin/components/featureflags/featureflags.component.scss new file mode 100644 index 0000000..1aa47e8 --- /dev/null +++ b/src/app/admin/components/featureflags/featureflags.component.scss @@ -0,0 +1,8 @@ +.featureflags { + .form-group { + margin-left: 5px; + } + label { + margin-left: 3px; + } +} \ No newline at end of file diff --git a/src/app/admin/components/featureflags/featureflags.component.spec.ts b/src/app/admin/components/featureflags/featureflags.component.spec.ts new file mode 100644 index 0000000..2d31e29 --- /dev/null +++ b/src/app/admin/components/featureflags/featureflags.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FeatureflagsComponent } from './featureflags.component'; + +describe('FeatureflagsComponent', () => { + let component: FeatureflagsComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [FeatureflagsComponent] + }); + fixture = TestBed.createComponent(FeatureflagsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/components/featureflags/featureflags.component.ts b/src/app/admin/components/featureflags/featureflags.component.ts new file mode 100644 index 0000000..d72e250 --- /dev/null +++ b/src/app/admin/components/featureflags/featureflags.component.ts @@ -0,0 +1,59 @@ +import {Component, OnDestroy, OnInit} from '@angular/core'; +import {ApiService, FeatureFlagStateDto} from "../../../services/api.service"; +import {FeatureFlagList} from "../../../shared/featureflags"; +import {takeUntil} from "rxjs/operators"; +import {Subject} from "rxjs"; +import {EventService} from "../../../services/event.service"; + +@Component({ + selector: 'app-featureflags', + templateUrl: './featureflags.component.html', + styleUrls: ['./featureflags.component.scss'] +}) +export class FeatureflagsComponent implements OnInit, OnDestroy { + destroyed$ = new Subject(); + public features: FeatureFlagStateDto[] = []; + constructor(private apiService: ApiService, private eventService: EventService) { + } + + ngOnDestroy(): void { + this.destroyed$.complete(); + } + ngOnInit(): void { + this.eventService.featureFlagChanged.pipe(takeUntil(this.destroyed$)).subscribe(result => this.loadFeatureFlags()); + this.loadFeatureFlags(); + } + private loadFeatureFlags() { + + FeatureFlagList.FeatureFlags.map((featureFlag) => { + this.apiService.getFeatureFlagState(featureFlag).pipe(takeUntil(this.destroyed$)).subscribe((result) => { + if(!this.features.find((x) => x.name === result.name)) { + this.features.push(result); + this.features.sort((a, b) => { + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + return 0; + }); + } else { + const index = this.features.findIndex((x) => x.name === result.name); + this.features[index] = result; + } + }); + }) + } + + setFeatureFlag(name: string) { + const ff = this.features.find((featureFlag) => featureFlag.name === name); + let newState = false; + if(ff) { + newState = !ff.state; + } + this.apiService.setFeatureFlagState(name, newState).pipe(takeUntil(this.destroyed$)).subscribe((result) => { + this.loadFeatureFlags(); + }); + } +} diff --git a/src/app/admin/components/main-actions/main-actions.component.ts b/src/app/admin/components/main-actions/main-actions.component.ts index f5c5c3c..6925519 100644 --- a/src/app/admin/components/main-actions/main-actions.component.ts +++ b/src/app/admin/components/main-actions/main-actions.component.ts @@ -25,6 +25,7 @@ export class MainActionsComponent implements OnInit { { title: 'Registration', name: 'register'}, { title: 'Onboarding', name: 'onboarding' }, { title: 'Start quiz', name: 'quiz' }, + { title: 'Endgame Points', name: 'endgamepoints' }, { title: 'End', name: 'finish' }, ]; diff --git a/src/app/admin/components/queue-actions/queue-actions.component.html b/src/app/admin/components/queue-actions/queue-actions.component.html index cc698f5..1421ccf 100644 --- a/src/app/admin/components/queue-actions/queue-actions.component.html +++ b/src/app/admin/components/queue-actions/queue-actions.component.html @@ -3,3 +3,8 @@
tg: {{ gameQueue.type }}
+
+

Who won

+ + +
\ No newline at end of file diff --git a/src/app/admin/components/queue-actions/queue-actions.component.ts b/src/app/admin/components/queue-actions/queue-actions.component.ts index fb00be4..c69a02f 100644 --- a/src/app/admin/components/queue-actions/queue-actions.component.ts +++ b/src/app/admin/components/queue-actions/queue-actions.component.ts @@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { EventService } from "../../../services/event.service"; import { Subject } from "rxjs"; import { map, takeUntil } from "rxjs/operators"; -import { EventGameQueue, QueueTypes } from "../../../../types/server-event"; +import {EventGameQueue, QueueTypes, VersusBeginEvent} from "../../../../types/server-event"; import { ApiService } from "../../../services/api.service"; @Component({ @@ -14,7 +14,7 @@ export class QueueActionsComponent implements OnInit, OnDestroy { destroyed$ = new Subject() constructor(private eventService: EventService, private apiService: ApiService) { } gameQueue: EventGameQueue | null; - + versusData: VersusBeginEvent| null = null; ngOnInit(): void { this.eventService.gameQueueEvent.pipe( takeUntil(this.destroyed$), @@ -22,7 +22,17 @@ export class QueueActionsComponent implements OnInit, OnDestroy { ).subscribe(e => { this.gameQueue = e; }); + this.setVersusHandler(); } + + setVersusHandler() { + this.eventService.versusBegin.pipe(takeUntil(this.destroyed$)).subscribe((r) => { + this.versusData = r.data; + + }); + + } + ngOnDestroy() { this.destroyed$.complete(); } @@ -32,4 +42,10 @@ export class QueueActionsComponent implements OnInit, OnDestroy { // this.gameQueue = null; }) } + + versusWon(playerId: number, loser: number) { + this.apiService.completeVersus(playerId, loser).subscribe(r => { + this.versusData = null; + }) + } } diff --git a/src/app/admin/configuration/configuration.component.html b/src/app/admin/configuration/configuration.component.html new file mode 100644 index 0000000..efab6d3 --- /dev/null +++ b/src/app/admin/configuration/configuration.component.html @@ -0,0 +1,4 @@ +
+

FeatureFlags

+ +
diff --git a/src/app/admin/configuration/configuration.component.scss b/src/app/admin/configuration/configuration.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/admin/configuration/configuration.component.spec.ts b/src/app/admin/configuration/configuration.component.spec.ts new file mode 100644 index 0000000..f9a950d --- /dev/null +++ b/src/app/admin/configuration/configuration.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConfigurationComponent } from './configuration.component'; + +describe('ConfigurationComponent', () => { + let component: ConfigurationComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ConfigurationComponent] + }); + fixture = TestBed.createComponent(ConfigurationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/configuration/configuration.component.ts b/src/app/admin/configuration/configuration.component.ts new file mode 100644 index 0000000..56d6668 --- /dev/null +++ b/src/app/admin/configuration/configuration.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-configuration', + templateUrl: './configuration.component.html', + styleUrls: ['./configuration.component.scss'] +}) +export class ConfigurationComponent { + +} diff --git a/src/app/admin/home/home.component.html b/src/app/admin/home/home.component.html index 53771d1..2dd4ab4 100644 --- a/src/app/admin/home/home.component.html +++ b/src/app/admin/home/home.component.html @@ -1,8 +1,9 @@ -
- - - - - - -
+ + +
+

Queue

+ + +
diff --git a/src/app/admin/home/home.component.scss b/src/app/admin/home/home.component.scss index e69de29..5e69929 100644 --- a/src/app/admin/home/home.component.scss +++ b/src/app/admin/home/home.component.scss @@ -0,0 +1,5 @@ +@import "../../../styles"; +.nav { + background-color: $thg_orange; + +} \ No newline at end of file diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 02f5a9f..e6ac10a 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -6,6 +6,7 @@ import { RegisterComponent } from "./views/register/register.component"; import { OnboardingComponent } from "./views/onboarding/onboarding.component"; import { InitialComponent } from './views/initial/initial.component'; import { FinishComponent } from './views/finish/finish.component'; +import {EndgamepointsComponent} from "./views/endgamepoints/endgamepoints.component"; const routes: Routes = [ { path: 'quiz', component: QuizComponent }, @@ -14,6 +15,7 @@ const routes: Routes = [ { path: 'onboarding', component: OnboardingComponent }, { path: 'initial', component: InitialComponent }, { path: 'finish', component: FinishComponent }, + { path: 'endgamepoints', component: EndgamepointsComponent }, { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)}, ]; diff --git a/src/app/app.component.html b/src/app/app.component.html index bbd7a4b..518d5f2 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,5 +1,7 @@ - + + + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7f8d511..a0aa6c9 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,25 +1,40 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { io, Socket } from "socket.io-client"; -import { API_URL, WEBSOCK_URL } from '../app.constants'; import { EventService } from "./services/event.service"; -import { EventStateChanged, ServerEvent } from "../types/server-event"; +import {EventStateChanged, ServerEvent, VersusBeginEvent} from "../types/server-event"; import { ApiService } from "./services/api.service"; import { ActivatedRoute, Router } from "@angular/router"; import { filter, map, takeUntil } from "rxjs/operators"; import { ToastService } from "./toast.service"; import { VoiceService } from "./services/voice.service"; -import { Subject } from "rxjs"; +import {delay, delayWhen, Subject} from "rxjs"; import { getAudioPath } from "./helper/tts.helper"; +import {animate, keyframes, style, transition, trigger} from "@angular/animations"; +import {environment} from "../environments/environment"; @Component({ selector: 'app-root', templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] + styleUrls: ['./app.component.scss'], + animations: [ + trigger( + 'enterAnimation', [ + transition(':enter', [ + style({transform: 'translateX(100%)', opacity: 0}), + animate('500ms', style({transform: 'translateX(0)', opacity: 1})) + ]), + transition(':leave', [ + style({transform: 'translateX(0)', opacity: 1}), + animate('2000ms', style({transform: 'translateX(100%)', opacity: 0})) + ]) + ] + )] }) export class AppComponent implements OnInit, OnDestroy { title = 'thanksgiving'; - connection = io(WEBSOCK_URL, { transports: ['websocket']}); + connection = io(environment.WEBSOCK_URL, { transports: ['websocket']}); destroyed = new Subject(); + versusData: VersusBeginEvent|null = null; audioSrc: string; constructor( @@ -38,10 +53,12 @@ export class AppComponent implements OnInit, OnDestroy { console.log(data); this.eventService.emit(data); }); - this.apiService.getAppState('main').subscribe((result) => { - this.router.navigate([`/${result.value}`]).then(() => { - console.log(`navigated to ${result.value}`); - }) + this.apiService.getAppState('main').pipe(takeUntil(this.destroyed),delay(300)).subscribe((result) => { + if(this.router.url.indexOf('admin') === -1 || window.location.href.indexOf('admin') === -1) { + this.router.navigate([`/${result.value}`]).then(() => { + console.log(`navigated to ${result.value}`); + }) + } }); this.eventService.stateChangedEvent.pipe( map(e => e.data), @@ -55,6 +72,7 @@ export class AppComponent implements OnInit, OnDestroy { console.log(text); this.audioSrc = text; }) + this.setupVersusHandler(); } ngOnDestroy() { this.destroyed.complete(); @@ -63,4 +81,16 @@ export class AppComponent implements OnInit, OnDestroy { onAudioEnded() { this.voiceService.audioEnded(); } + + private setupVersusHandler() { + this.eventService.versusBegin.pipe(takeUntil(this.destroyed)).subscribe(r => { + if (this.router.url.indexOf('admin') === -1) { + this.versusData = r.data; + } + }) + this.eventService.versusEnd.pipe(takeUntil(this.destroyed)).subscribe((r) => { + console.log(r); + this.versusData = null; + }) + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index f2a7954..abee8ec 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -27,6 +27,8 @@ import { AvatarComponent } from './components/avatar/avatar.component'; import { FinishComponent } from './views/finish/finish.component'; import { InitialComponent } from './views/initial/initial.component'; import { SkrepaComponent } from './components/skrepa/skrepa.component'; +import { VersusComponent } from './components/versus/versus.component'; +import { EndgamepointsComponent } from './views/endgamepoints/endgamepoints.component'; @NgModule({ declarations: [ @@ -50,6 +52,8 @@ import { SkrepaComponent } from './components/skrepa/skrepa.component'; FinishComponent, InitialComponent, SkrepaComponent, + VersusComponent, + EndgamepointsComponent, ], imports: [ BrowserModule, diff --git a/src/app/components/answer-notification/answer-notification.component.html b/src/app/components/answer-notification/answer-notification.component.html index 2c8d376..e9ceced 100644 --- a/src/app/components/answer-notification/answer-notification.component.html +++ b/src/app/components/answer-notification/answer-notification.component.html @@ -3,8 +3,10 @@

🎉 Ура, правильный ответ!

❌ А вот и нет! ❌

-
- +
+ + +

выйграл наказание

diff --git a/src/app/components/answer-notification/answer-notification.component.ts b/src/app/components/answer-notification/answer-notification.component.ts index dc32863..c23b264 100644 --- a/src/app/components/answer-notification/answer-notification.component.ts +++ b/src/app/components/answer-notification/answer-notification.component.ts @@ -1,6 +1,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { ApiService } from "../../services/api.service"; -import { interval, Observable, Subject } from "rxjs"; +import {concatMap, interval, Observable, Subject} from "rxjs"; import { EventService } from "../../services/event.service"; import { filter, map, take, takeUntil, tap } from "rxjs/operators"; import { Participant } from "../../../types/participant"; @@ -51,14 +51,12 @@ import { VoiceService } from "../../services/voice.service"; export class AnswerNotificationComponent implements OnInit, OnDestroy { isShown = false; answerIsValid = false; - participant: Participant; - timer: Observable; countdown = 10; showCountdown = false; announceAudio = true; + participants: Participant[] = []; audioSrc: string; private destroyed$ = new Subject(); - constructor(private apiService: ApiService, private eventService: EventService, private voiceService: VoiceService) { this.eventService.answerReceivedEvent.pipe( takeUntil(this.destroyed$), @@ -68,20 +66,23 @@ export class AnswerNotificationComponent implements OnInit, OnDestroy { takeUntil(this.destroyed$), map(e => e.data) ).subscribe(d => this.showNotification(d.telegramId, false, d.validAnswer, null)); - this.eventService.scoreChangedEvent.pipe( - takeUntil(this.destroyed$), - map(e => e.data), - ).subscribe(e => { - if(e.telegramId === this.participant.telegramId) { - this.participant.score = e.newScore - } - }); + // this.eventService.scoreChangedEvent.pipe( + // takeUntil(this.destroyed$), + // map(e => e.data), + // ).subscribe(e => { + // if(e.telegramId === this.participant.telegramId) { + // this.participant.score = e.newScore + // } + // }); + + } showNotification(telegramId: number, validAnswer: boolean, validAnswerValue: string, note: string|null) { - this.countdown = validAnswer ? 10 : 5; + console.log(`showNotification`); this.apiService.getParticipant(telegramId).subscribe(p => { - this.participant = p; + this.countdown = validAnswer ? 10 : 5; + this.participants.push(p); this.isShown = true; this.answerIsValid = validAnswer; const template = validAnswer ? 'announce-valid' : 'announce-invalid'; @@ -89,7 +90,10 @@ export class AnswerNotificationComponent implements OnInit, OnDestroy { templateData['user'] = p.name; templateData['answer'] = validAnswerValue; templateData['user-genitive'] = p.properties.genitive; - this.voiceService.playAudio(getAudioPathWithTemplate(template, '', templateData)); + if(this.participants.length === 1) { + this.voiceService.playAudio(getAudioPathWithTemplate(template, '', templateData)); + } + //this.voiceService.playAudio(getAudioPathWithTemplate(template, '', templateData)); this.voiceService.audioEndedSubject.pipe(takeUntil(this.destroyed$), take(1)).subscribe(r => { if (note && validAnswer) { this.voiceService.playAudio(getAudioPath(note)) @@ -101,12 +105,14 @@ export class AnswerNotificationComponent implements OnInit, OnDestroy { } countdownCompleted() { + this.participants = []; console.log(`countdown-completed`); this.showCountdown = false; this.isShown = false; this.announceAudio = false; this.countdown = 10; - this.apiService.continueGame().subscribe(r => console.log(r)); + // this.apiService.continueGame().subscribe(r => console.log(r)); + } ngOnInit(): void { diff --git a/src/app/components/card-played/card-played.component.ts b/src/app/components/card-played/card-played.component.ts index c3de195..2291bb2 100644 --- a/src/app/components/card-played/card-played.component.ts +++ b/src/app/components/card-played/card-played.component.ts @@ -1,12 +1,11 @@ import { Component, OnInit } from '@angular/core'; import { EventService } from "../../services/event.service"; import { filter, map } from "rxjs/operators"; -import { EventCardPlayed } from "../../../types/server-event"; import { ApiService } from "../../services/api.service"; import { animate, style, transition, trigger } from "@angular/animations"; -import { API_URL } from "../../../app.constants"; import { getAudioPath } from "../../helper/tts.helper"; import { VoiceService } from "../../services/voice.service"; +import {environment} from "../../../environments/environment"; @Component({ selector: 'app-card-played', @@ -59,7 +58,7 @@ export class CardPlayedComponent implements OnInit { }) } getImageUrl() { - return `${API_URL}/guests/photo/${this.participantId}?$t=${this.imgTimestamp}`; + return `${environment.API_URL}/guests/photo/${this.participantId}?$t=${this.imgTimestamp}`; } getAudioSrc(text: string) { diff --git a/src/app/components/cards-history/cards-history.component.scss b/src/app/components/cards-history/cards-history.component.scss index ae0a97f..707c824 100644 --- a/src/app/components/cards-history/cards-history.component.scss +++ b/src/app/components/cards-history/cards-history.component.scss @@ -2,7 +2,7 @@ .cards-history { position: fixed; - z-index: 20000; + z-index: 1000; width: 100%; bottom: 0; max-height: 70px; diff --git a/src/app/components/game-pause/game-pause.component.ts b/src/app/components/game-pause/game-pause.component.ts index f772170..663f371 100644 --- a/src/app/components/game-pause/game-pause.component.ts +++ b/src/app/components/game-pause/game-pause.component.ts @@ -9,7 +9,7 @@ import { getAudioPath } from "../../helper/tts.helper"; }) export class GamePauseComponent implements OnInit, OnDestroy { tstamp = new Date().getTime(); - private interval: number; + private interval: NodeJS.Timeout; constructor(private voiceService: VoiceService) { } diff --git a/src/app/components/game-queue/game-queue.component.html b/src/app/components/game-queue/game-queue.component.html index b0a1549..0851407 100644 --- a/src/app/components/game-queue/game-queue.component.html +++ b/src/app/components/game-queue/game-queue.component.html @@ -1,10 +1,14 @@ -
+
-
-
- +
+
+
-
+

Ура, приз!

@@ -27,12 +31,34 @@
- +
+
+
+
+

Ответили правильно

+
+
+
+ +
+
+
+

Не смогли

+
+
+
+ +
+
+
+
+

Результаты (не утешительные)

+

Так вышло, что никто не ответил на вопросы вообще

+
- -
- -
+
+ +
\ No newline at end of file diff --git a/src/app/components/game-queue/game-queue.component.scss b/src/app/components/game-queue/game-queue.component.scss index 7aca17c..e693b83 100644 --- a/src/app/components/game-queue/game-queue.component.scss +++ b/src/app/components/game-queue/game-queue.component.scss @@ -11,6 +11,10 @@ to { background-color: $thg_orange } } +@keyframes results { + from { background-color: inherit } + to { background-color: $thg_yellow } +} .queue-container { width: 100%; @@ -18,6 +22,7 @@ display: flex; align-items: center; justify-content: center; + z-index: 100; } .queue-info { @@ -40,3 +45,16 @@ h1,h3 { color: white; background-color: $thg_orange; } + +.results { + animation: results 3s 1; + background-color: $thg_yellow; + color: black; +} + +.versus-container { + position: absolute; + width: 100%; + height: 100vh; + background-color: $thg_red; +} \ No newline at end of file diff --git a/src/app/components/game-queue/game-queue.component.ts b/src/app/components/game-queue/game-queue.component.ts index 868d12e..596fd11 100644 --- a/src/app/components/game-queue/game-queue.component.ts +++ b/src/app/components/game-queue/game-queue.component.ts @@ -3,21 +3,37 @@ import { EventGameQueue, QueueTypes } from "../../../types/server-event"; import { Participant } from "../../../types/participant"; import { ApiService } from "../../services/api.service"; import { Subject } from "rxjs"; -import { takeUntil } from "rxjs/operators"; +import {map, take, takeUntil} from "rxjs/operators"; import { Question } from "../../../types/question"; import { getAudioPath } from "../../helper/tts.helper"; import { PrizeDto } from "../../../types/prize.dto"; + +class ResultEntity { + valid: { + user: number; + time: Date; + valid: boolean; + }[]; + invalid: { + user: number; + time: Date; + valid: boolean; + }[]; +} @Component({ selector: 'app-game-queue', templateUrl: './game-queue.component.html', styleUrls: ['./game-queue.component.scss'] }) + export class GameQueueComponent implements OnInit { @Input() action: EventGameQueue; readonly gameQueueTypes = QueueTypes - participant: Participant; + participant: Participant | null; + participants: Participant[] = []; destroyed$ = new Subject(); + results: ResultEntity; penalty = ''; countdown: number; showCountdown: boolean; @@ -31,11 +47,14 @@ export class GameQueueComponent implements OnInit { constructor(private apiService: ApiService) { } ngOnInit(): void { - this.apiService.getParticipant(this.action.target).pipe( + if(this.action.target) { + this.apiService.getParticipant(this.action.target).pipe( takeUntil(this.destroyed$) - ).subscribe(e => { - this.participant = e; - }); + ).subscribe(e => { + this.participant = e; + }); + } + if(this.action.type === this.gameQueueTypes.penalty) { this.getPenalty(); } @@ -59,7 +78,12 @@ export class GameQueueComponent implements OnInit { this.screpaText = this.action.text ?? ''; } + + if(this.action.type == this.gameQueueTypes.showresults) { + this.getResults(); + } console.log(this.action); + } getPenalty() { @@ -98,10 +122,36 @@ export class GameQueueComponent implements OnInit { } private getPrize() { + if(!this.participant === null) { + return; + } this.apiService.getPrize().pipe(takeUntil(this.destroyed$)).subscribe((r) => { this.prize = r; this.showPrize = true; - this.prizeAudioSrc = getAudioPath(`Поздравляю, ${this.participant.name} получает ${this.prize.name}`); + this.prizeAudioSrc = getAudioPath(`Поздравляю, ${this.participant?.name} получает ${this.prize.name}`); }); } + + private getResults() { + this.apiService.getQuestionResults().pipe(takeUntil(this.destroyed$), map(result => { + result.map(r => { + this.apiService.getParticipant(r.user).pipe(takeUntil(this.destroyed$)).subscribe((particip) => { + if(!this.participants[r.user]) { + this.participants[r.user] = particip; + } + }) + }) + return result; + }) + ).subscribe((results) => { + this.results = { + valid: [], + invalid: [], + } + let sortedByTime = results.sort((a,b) => a.time.getTime() - b.time.getTime()); + this.results.valid = sortedByTime.filter((r) => r.valid); + this.results.invalid = sortedByTime.filter((r) => !r.valid); + console.log(this.results) + }) + } } diff --git a/src/app/components/participant-item/participant-item.component.html b/src/app/components/participant-item/participant-item.component.html index 85bf2df..b2804d4 100644 --- a/src/app/components/participant-item/participant-item.component.html +++ b/src/app/components/participant-item/participant-item.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/components/participant-item/participant-item.component.scss b/src/app/components/participant-item/participant-item.component.scss index dba52f1..98d6079 100644 --- a/src/app/components/participant-item/participant-item.component.scss +++ b/src/app/components/participant-item/participant-item.component.scss @@ -1,8 +1,8 @@ @import "../../../styles.scss"; @import url('https://fonts.googleapis.com/css2?family=Pacifico&display=swap'); .card { - min-width: 150px; - max-width: 150px; + min-width: 140px; + max-width: 140px; min-height: 230px; border: 0px solid #c2c2c2; background: rgb(255,166,1); @@ -11,10 +11,15 @@ padding: 0px; } +.transparent { + background: inherit; + max-width: 200px; +} + figure { border-radius:100%; display:inline-block; - margin-bottom: 15px; + margin-bottom: 5px; margin-left: auto; margin-right: auto; } @@ -62,7 +67,7 @@ figure { } .big { - font-size: 7em; + font-size: 3em; color: $thg_green; transition-delay: 2s; diff --git a/src/app/components/participant-item/participant-item.component.ts b/src/app/components/participant-item/participant-item.component.ts index 10c3b8a..235716f 100644 --- a/src/app/components/participant-item/participant-item.component.ts +++ b/src/app/components/participant-item/participant-item.component.ts @@ -4,9 +4,9 @@ import { EventService } from "../../services/event.service"; import { Observable, Subject, Subscription } from "rxjs"; import { filter, map, takeUntil } from "rxjs/operators"; import { EventCardPlayed, EventCardsChanged, EventPhotosUpdated, ServerEvent } from "../../../types/server-event"; -import { API_URL } from "../../../app.constants"; import { ApiService } from "../../services/api.service"; import { CardItem } from "../../../types/card-item"; +import {environment} from "../../../environments/environment"; @Component({ selector: 'app-participant-item', @@ -23,6 +23,8 @@ export class ParticipantItemComponent implements OnInit, OnDestroy, OnChanges { imgTimestamp = (new Date()).getTime(); addAnimatedClass = false; @Input() bannedRemaining: number|undefined = 0; + @Input() transparent = false; + @Input() shadow = true; constructor(private eventService: EventService, private apiService: ApiService) { } @@ -62,13 +64,18 @@ export class ParticipantItemComponent implements OnInit, OnDestroy, OnChanges { } getCards() { - this.apiService.getCards(this.participant.telegramId).subscribe((r) => { - this.cards = r; - }) + if(this.participant) { + this.apiService.getCards(this.participant.telegramId).subscribe((r) => { + this.cards = r; + }) + } } getImageUrl() { - return `${API_URL}/guests/photo/${this.participant.telegramId}?$t=${this.imgTimestamp}`; + if(this.participant) { + return `${environment.API_URL}/guests/photo/${this.participant.telegramId}?$t=${this.imgTimestamp}`; + } + return null; } } diff --git a/src/app/components/participants/participants.component.html b/src/app/components/participants/participants.component.html index 24fe064..f59a88c 100644 --- a/src/app/components/participants/participants.component.html +++ b/src/app/components/participants/participants.component.html @@ -1,10 +1,9 @@
-
+
-
diff --git a/src/app/components/question/question.component.html b/src/app/components/question/question.component.html index 3ad7618..a2cc5e6 100644 --- a/src/app/components/question/question.component.html +++ b/src/app/components/question/question.component.html @@ -1,10 +1,10 @@
-

- Вопрос +

+ Вопрос

-

+

{{ question.text }}

@@ -15,7 +15,14 @@
+
+
+
+
+
+ {{ countdown }} +
\ No newline at end of file diff --git a/src/app/components/question/question.component.scss b/src/app/components/question/question.component.scss index 9524995..b0bc1a7 100644 --- a/src/app/components/question/question.component.scss +++ b/src/app/components/question/question.component.scss @@ -25,8 +25,7 @@ section { margin: 15px; background: $yellow_gradient; font-size: 1.5em; - padding: 10px; - padding-top: 20px; + padding: 20px 10px 10px; border-radius: 23px; p { text-align: center; @@ -34,3 +33,21 @@ section { } } } + +.countdown { + &.warn { + color: $thg_red; + transition: color 2000ms linear, font-size 5000ms ease; + border-radius: 10px; + font-size: 3em; + } + min-width: 40px; + position: absolute; + bottom: 60px; + right: 20px; + span { + font-size: 3em; + font-weight: bold; + } + color: $thg_brown; +} \ No newline at end of file diff --git a/src/app/components/question/question.component.ts b/src/app/components/question/question.component.ts index 5fcfa98..a6e885c 100644 --- a/src/app/components/question/question.component.ts +++ b/src/app/components/question/question.component.ts @@ -15,6 +15,9 @@ export class QuestionComponent implements OnInit, OnDestroy { @Input() question: Question; destroyed$ = new Subject(); private questionSubscription: Subscription; + countdownInterval:ReturnType|null= null; + countdown = 0; + readonly countDownTimer = 20; constructor(private apiService:ApiService, private eventService: EventService, private voiceService: VoiceService) { } @@ -24,10 +27,44 @@ export class QuestionComponent implements OnInit, OnDestroy { return; } setTimeout(() => this.getQuestion(), 3000); - this.questionSubscription = this.eventService.questionChangedEvent.subscribe(() =>{ - this.getQuestion(); + + this.eventService.gameQueueEvent.pipe(takeUntil(this.destroyed$)).subscribe(() => { + this.countdown = -1; }); + this.startCountdown(); + + this.questionSubscription = this.eventService.questionChangedEvent.subscribe(() =>{ + this.getQuestion(); + this.countdown = 20; + }); + this.setUpVersusHandler(); + } + startCountdown() { + this.countdown = this.countDownTimer; + this.countdownInterval = setInterval(() => { + if(this.countdown === 0) { + this.continueGame(); + } + this.countdown--; + }, 1000) + } + + setUpVersusHandler() { + this.eventService.versusBegin.pipe(takeUntil(this.destroyed$)).subscribe((r) => { + if(this.countdownInterval) { + clearInterval(this.countdownInterval); + } + }); + this.eventService.versusBegin.pipe(takeUntil(this.destroyed$)).subscribe((r) => { + this.startCountdown(); + }) + } + + continueGame() { + this.apiService.continueGame().subscribe((r) => { + console.log(r); + }); } getQuestion() { diff --git a/src/app/components/versus/versus.component.html b/src/app/components/versus/versus.component.html new file mode 100644 index 0000000..9755b3d --- /dev/null +++ b/src/app/components/versus/versus.component.html @@ -0,0 +1,25 @@ +
+
+
+ + +
+
+ + + +
+
+
+
+
+

{{ versusData.text}}

+
+
+
{{ versusData.description }}
+
+
+ +
+
+ \ No newline at end of file diff --git a/src/app/components/versus/versus.component.scss b/src/app/components/versus/versus.component.scss new file mode 100644 index 0000000..bbd3b21 --- /dev/null +++ b/src/app/components/versus/versus.component.scss @@ -0,0 +1,115 @@ +@import '../../../styles'; +@keyframes slideDown { + 0% { + top: -100vh; + } + 100% { + top: 0; + } +} +@keyframes slideRight { + 0% { + left: -100vh; + } + 100% { + left: 0; + } +} + +@keyframes slideLeft { + 0% { + right: -100vh; + } + 100% { + right: 0; + } +} + +@keyframes slideInUp { + 0% { + bottom: -100px; + } + 100% { + bottom: 25%; + } +} + +@keyframes opacityIn { + 0% { + opacity: 0; + } + 100% { + opacity: 0.84; + } +} + + + + +.versus { + background-color: $thg_brown; + z-index: 20000; + position: fixed; + top: -100vh; + left: 0; + width: 100%; + height: 100vh; + animation: slideDown 1s ease forwards; +} +.versus:before { + content: "VS"; + position: absolute; + font-size: 20vw; /* Large size for the background */ + color: rgba(255, 255, 255, 0.2); /* Light opacity */ + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-weight: bold; + z-index: 22000; /* Puts it behind other content */ + pointer-events: none; +} + +.players { + height: 100vh; +} + +/* Left player area */ +.player-one { + position: relative; + width: 50%; + height: 100%; + background-color: #4a90e2; + clip-path: polygon(0 0, 100% 0, 50% 100%, 0 100%); + display: flex; + justify-content: center; + align-items: center; + color: white; + font-size: 2rem; + font-weight: bold; + animation: slideRight 2s ease forwards; +} + +/* Right player area */ +.player-two { + position: relative; + width: 50%; + height: 100%; + background-color: #d9534f; + clip-path: polygon(50% 0, 100% 0, 100% 100%, 0 100%); + display: flex; + justify-content: center; + align-items: center; + color: white; + font-size: 2rem; + font-weight: bold; + animation: slideLeft 2s ease forwards; +} + +.task { + position: absolute; + bottom: 0; + width: 50%; + background-color: white; + opacity: 0.84; + animation: slideInUp 3s ease forwards, opacityIn 1500ms linear; +} \ No newline at end of file diff --git a/src/app/components/versus/versus.component.spec.ts b/src/app/components/versus/versus.component.spec.ts new file mode 100644 index 0000000..2cad49d --- /dev/null +++ b/src/app/components/versus/versus.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VersusComponent } from './versus.component'; + +describe('VersusComponent', () => { + let component: VersusComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [VersusComponent] + }); + fixture = TestBed.createComponent(VersusComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/versus/versus.component.ts b/src/app/components/versus/versus.component.ts new file mode 100644 index 0000000..f938d6a --- /dev/null +++ b/src/app/components/versus/versus.component.ts @@ -0,0 +1,57 @@ +import {Component, Input, OnDestroy, OnInit} from '@angular/core'; +import {ApiService} from "../../services/api.service"; +import {combineLatest, Subject} from "rxjs"; +import {Participant} from "../../../types/participant"; +import {VersusItem} from "../../../types/versus-item"; +import {VoiceService} from "../../services/voice.service"; +import {getAudioPath} from "../../helper/tts.helper"; +import {takeUntil} from "rxjs/operators"; + +@Component({ + selector: 'app-versus', + templateUrl: './versus.component.html', + styleUrls: ['./versus.component.scss'] +}) +export class VersusComponent implements OnInit, OnDestroy{ + @Input() player1: number; + @Input() player2: number; + player1data: Participant; + player2data: Participant; + destroyed$ = new Subject(); + playersLoaded = false; + versusData: VersusItem | null = null; + + constructor(private apiService: ApiService, private voiceService: VoiceService) { + } + ngOnInit() { + this.loadPlayersData(); + this.loadTask(); + this.playAudio(); + } + ngOnDestroy() { + this.destroyed$.complete(); + } + + playAudio() { + this.voiceService.playAudio(getAudioPath('Схватка!')); + } + + loadPlayersData() { + const player1Data$ = this.apiService.getParticipant(this.player1); + const player2Data$ = this.apiService.getParticipant(this.player2); + combineLatest([player1Data$,player2Data$]).pipe(takeUntil(this.destroyed$)).subscribe(([d1,d2]) => { + this.player1data = d1; + this.player2data = d2; + this.playersLoaded = true; + }) + } + + private loadTask() { + setTimeout(() => { + this.apiService.getVersus().pipe(takeUntil(this.destroyed$)).subscribe((r) => { + this.versusData = r; + }) + }, 1500); + + } +} diff --git a/src/app/helper/tts.helper.ts b/src/app/helper/tts.helper.ts index f6f0090..0cd8eee 100644 --- a/src/app/helper/tts.helper.ts +++ b/src/app/helper/tts.helper.ts @@ -1,9 +1,10 @@ -import { API_URL } from "../../app.constants"; +import {environment} from "../../environments/environment"; + export function getAudioPath(text: string, voice: number = 1) { - return `${API_URL}/voice/tts?text=${text}&voice=${voice}`; + return `${environment.API_URL}/voice/tts?text=${text}&voice=${voice}`; } export function getAudioPathWithTemplate(path: string, text: string, vars: { [index: string]: string }) { const t = new Date().getTime(); - return `${API_URL}/voice/${path}?text=${text}&vars=${JSON.stringify(vars)}&t=${t}`; + return `${environment.API_URL}/voice/${path}?text=${text}&vars=${JSON.stringify(vars)}&t=${t}`; } diff --git a/src/app/services/api.service.ts b/src/app/services/api.service.ts index cb0332d..ee603ca 100644 --- a/src/app/services/api.service.ts +++ b/src/app/services/api.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs"; -import { API_URL } from "../../app.constants"; import { AppState } from "../../types/app-state"; import { Participant } from "../../types/participant"; import { Question } from "../../types/question"; @@ -9,84 +8,172 @@ import { CardItem } from "../../types/card-item"; import { GameState } from "./gameState"; import { PenaltyDto } from "../../types/penalty.dto"; import { PrizeDto } from "../../types/prize.dto"; +import {QuestionresultsDto} from "../../types/questionresults.dto"; +import {map} from "rxjs/operators"; +import {VersusItem} from "../../types/versus-item"; +import {environment} from "../../environments/environment"; + +export class FeatureFlagStateDto { + name: string; + state: boolean; +} + +export interface StateInformationDto { + key: string; + value: { key: string; value: T }; +} + +export interface StateInformationVersusDto { + action: any; + player1: number; + player2: number; +} + +export interface ConfigRecordDto { + key: string; + value: string; +} + +export interface EndgameResultsDto { + maxInvalidAnswers: { + id: number; + count: number; + name: string; + }, + maxRewards: { + id: number; + count: number; + name: string; + }, + maxPenalties: { + id: number; + count: number; + name: string; + } +} @Injectable({ providedIn: 'root' }) export class ApiService { - constructor(private httpClient: HttpClient) { } + constructor(private httpClient: HttpClient) { + console.log(environment.API_URL); + } public getAppState(state: string): Observable { - return this.httpClient.get(`${API_URL}/state/${state}`); + return this.httpClient.get(`${environment.API_URL}/state/${state}`); } public getParticipants(): Observable { - return this.httpClient.get(`${API_URL}/guests`); + return this.httpClient.get(`${environment.API_URL}/guests`); } public getParticipant(id: number): Observable { - return this.httpClient.get(`${API_URL}/guests/${id}`); + return this.httpClient.get(`${environment.API_URL}/guests/${id}`); } public getQuestion(): Observable { - return this.httpClient.get(`${API_URL}/quiz`); + return this.httpClient.get(`${environment.API_URL}/quiz`); } public setAppState(state: string, value: string) { - return this.httpClient.post(`${API_URL}/state`, { + return this.httpClient.post(`${environment.API_URL}/state`, { state, value }); } getCards(telegramId: number): Observable { - return this.httpClient.get(`${API_URL}/cards/${telegramId}`); + return this.httpClient.get(`${environment.API_URL}/cards/${telegramId}`); } continueGame() { console.log(`continue game`); - return this.httpClient.post(`${API_URL}/quiz/proceed`, {}); + return this.httpClient.post(`${environment.API_URL}/quiz/proceed`, {}); + } + + questionTimeout() { + console.log(`continue game`); + return this.httpClient.post(`${environment.API_URL}/quiz/timeout`, {}); } markQueueAsCompleted(_id: string) { - return this.httpClient.post(`${API_URL}/game/${_id}/complete`, {}); + return this.httpClient.post(`${environment.API_URL}/game/${_id}/complete`, {}); } pauseGame() { - return this.httpClient.post(`${API_URL}/game/pause`, {}); + return this.httpClient.post(`${environment.API_URL}/game/pause`, {}); } resumeGame() { - return this.httpClient.post(`${API_URL}/game/resume`, {}); + return this.httpClient.post(`${environment.API_URL}/game/resume`, {}); } getGameState() { - return this.httpClient.get(`${API_URL}/game/state`); + return this.httpClient.get(`${environment.API_URL}/game/state`); } getPenalty() { console.log(`get penalty`); - return this.httpClient.get(`${API_URL}/penalty`); + return this.httpClient.get(`${environment.API_URL}/penalty`); } playExtraCards() { console.log(`play extra cards`); - return this.httpClient.get(`${API_URL}/game/playextracards`); + return this.httpClient.get(`${environment.API_URL}/game/playextracards`); } getAdditionalQuestion(target: number) { - return this.httpClient.post(`${API_URL}/quiz/extraquestion`, { + return this.httpClient.post(`${environment.API_URL}/quiz/extraquestion`, { telegramId: target, }); } getImageUrl(id: number) { const timestamp = new Date().getTime(); - return `${API_URL}/guests/photo/${id}?$t=${timestamp}}`; + return `${environment.API_URL}/guests/photo/${id}?$t=${timestamp}}`; } getPrize(): Observable { - return this.httpClient.get(`${API_URL}/gifts`); + return this.httpClient.get(`${environment.API_URL}/gifts`); + } + + getQuestionResults() { + return this.httpClient.get(`${environment.API_URL}/quiz/question-results`).pipe(map((data) => + data.map((item) => { + return { + ...item, + time: new Date(item.time) + } + }) + )); + } + + getFeatureFlagState(feature: string) { + return this.httpClient.get(`${environment.API_URL}/featureflag/${feature}`); + } + + setFeatureFlagState(feature: string, state: boolean) { + return this.httpClient.post(`${environment.API_URL}/featureflag`, { name: feature, state: state }); + } + + getStateDetails() { + return this.httpClient.get(`${environment.API_URL}/game/state-details`); + } + + getVersus() { + return this.httpClient.get(`${environment.API_URL}/versus`); + } + + completeVersus(winner: number, loser: number) { + return this.httpClient.post(`${environment.API_URL}/versus/complete`, { + winner: winner, + loser: loser + }); + } + + getEndgameResults() { + return this.httpClient.get(`${environment.API_URL}/quiz/endgame-results`) } } diff --git a/src/app/services/event.service.ts b/src/app/services/event.service.ts index 261b0e5..c963857 100644 --- a/src/app/services/event.service.ts +++ b/src/app/services/event.service.ts @@ -9,7 +9,7 @@ import { EventUserAdded, EventWrongAnswerReceived, QuestionChangedEvent, - ServerEvent, UserPropertyChanged + ServerEvent, UserPropertyChanged, VersusBeginEvent } from "../../types/server-event"; @Injectable({ @@ -31,6 +31,9 @@ export class EventService { public gameResumed = new EventEmitter>(); public notificationEvent = new EventEmitter>(); public userPropertyChanged = new EventEmitter>(); + public featureFlagChanged = new EventEmitter>() + public versusBegin = new EventEmitter>(); + public versusEnd = new EventEmitter> constructor() { } public emit(event: ServerEvent) { @@ -81,6 +84,15 @@ export class EventService { case "user_property_changed": this.userPropertyChanged.emit(event as ServerEvent); break; + case "feature_flag_changed": + this.featureFlagChanged.emit(event); + break; + case "begin_versus": + this.versusBegin.emit(event as ServerEvent); + break; + case "end_versus": + this.versusEnd.emit(event as ServerEvent<{winner: number}>); + break; } } } diff --git a/src/app/services/testing-api.service.ts b/src/app/services/testing-api.service.ts new file mode 100644 index 0000000..a5a16c0 --- /dev/null +++ b/src/app/services/testing-api.service.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@angular/core'; +import {HttpClient} from "@angular/common/http"; +import {environment} from "../../environments/environment"; + +@Injectable({ + providedIn: 'root' +}) +export class TestingApiService { + + constructor(private httpClient: HttpClient) { } + + public simulateVersus() { + return this.httpClient.post(`${environment.API_URL}/versus/simulate-versus`, {}); + } + + resetAllVersusTasksAsIncompleted() { + return this.httpClient.post(`${environment.API_URL}/versus/reset-all`, {}); + } + + resetAllPlayersScore() { + return this.httpClient.post(`${environment.API_URL}/guests/reset-score`, {}); + } + + clearGameQueue() { + return this.httpClient.post(`${environment.API_URL}/game/clear-queue`, {}); + } + + simulateEndGamePoints() { + return this.httpClient.post(`${environment.API_URL}/quiz/calculate-endgame-extrapoints`, {}) + } + + simulateValidAnswer() { + return this.httpClient.post(`${environment.API_URL}/game/simulate-valid-answer`, {}); + } +} diff --git a/src/app/services/testingapi.service.spec.ts b/src/app/services/testingapi.service.spec.ts new file mode 100644 index 0000000..65c79e7 --- /dev/null +++ b/src/app/services/testingapi.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { TestingApiService } from './testing-api.service'; + +describe('TestingapiService', () => { + let service: TestingApiService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(TestingApiService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/voice.service.ts b/src/app/services/voice.service.ts index 3341ca6..253061d 100644 --- a/src/app/services/voice.service.ts +++ b/src/app/services/voice.service.ts @@ -1,14 +1,24 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders, HttpParams } from "@angular/common/http"; -import { API_URL } from "../../app.constants"; -import { Subject } from "rxjs"; +import {delay, delayWhen, interval, Observable, of, Subject} from "rxjs"; +import {ApiService} from "./api.service"; +import {takeUntil, tap} from "rxjs/operators"; +import {SharedMethods} from "../shared/sharedmethods"; +import {environment} from "../../environments/environment"; @Injectable({ providedIn: 'root' }) export class VoiceService { + destroyed$ = new Subject(); + voiceDisabled = false; - constructor(private httpClient: HttpClient) { } + constructor(private httpClient: HttpClient, private apiService: ApiService) { + this.apiService.getFeatureFlagState("DisableVoice").pipe(takeUntil(this.destroyed$)) + .subscribe((result) => { + this.voiceDisabled = result.state; + }) + } public voiceSubject = new Subject(); public audioEndedSubject = new Subject(); @@ -17,12 +27,29 @@ export class VoiceService { this.voiceSubject.next(url); } + playAudio$(url: string) { + this.voiceSubject.next(url); + return new Observable((observer) => { + if(this.voiceDisabled) { + observer.next(null); + observer.complete(); + } + const subscription = this.audioEndedSubject.subscribe({ + next: () => { + observer.next(null); + observer.complete(); + } + }); + return () => subscription.unsubscribe(); + }).pipe(delayWhen(val => this.voiceDisabled ? interval(5000) : interval(0))); + } + getAudioUrl(text: string,voice: number = 1) { - return `${API_URL}/voice/tts?voice=${voice}&text=${text}` + return `${environment.API_URL}/voice/tts?voice=${voice}&text=${text}` } getAudioUrlSSML(text: string) { - return `${API_URL}/voice/ssml?text=${encodeURI(text)}` + return `${environment.API_URL}/voice/ssml?text=${encodeURI(text)}` } audioEnded() { diff --git a/src/app/shared/featureflags.ts b/src/app/shared/featureflags.ts new file mode 100644 index 0000000..f77ba4d --- /dev/null +++ b/src/app/shared/featureflags.ts @@ -0,0 +1,10 @@ +export class FeatureFlagList { + static readonly FeatureFlags: string[] = [ + "EnableEndgamePoints", + "DontMarkQuestionsAsCompleted", + "DisableVoice", + "ProdMode", + "EndgamePointsUseCssAnimation", + "StartVersusIfPlayersAnsweredInSameTime", + ]; +} \ No newline at end of file diff --git a/src/app/shared/sharedmethods.ts b/src/app/shared/sharedmethods.ts new file mode 100644 index 0000000..37be26b --- /dev/null +++ b/src/app/shared/sharedmethods.ts @@ -0,0 +1,5 @@ +export class SharedMethods { + static sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } +} \ No newline at end of file diff --git a/src/app/views/endgamepoints/endgamepoints.component.html b/src/app/views/endgamepoints/endgamepoints.component.html new file mode 100644 index 0000000..edae027 --- /dev/null +++ b/src/app/views/endgamepoints/endgamepoints.component.html @@ -0,0 +1,83 @@ + + +
+
+
+
+
+
+
+ Время наградить особо отличившихся! +
+
+ За тупость +2 + +
+ +
+
+
+
+ За тяжелую судьбу +2 + +
+ +
+
+
+
+ За полный успех -2 + +
+ +
+
+
+
+
+
+ + + +
+ + +
+
+ Время наградить особо отличившихся! +
+
+ За тупость +2 + +
+ +
+
+
+
+ За тяжелую судьбу +2 + +
+ +
+
+
+
+ За полный успех −2 + +
+ +
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/src/app/views/endgamepoints/endgamepoints.component.scss b/src/app/views/endgamepoints/endgamepoints.component.scss new file mode 100644 index 0000000..102b8a9 --- /dev/null +++ b/src/app/views/endgamepoints/endgamepoints.component.scss @@ -0,0 +1,202 @@ +$shooting-time: 3000ms; + +@keyframes slideInUp { + 0% { + transform: translateY(3000%); + } + 100% { + transform: translateY(0); + } +} + +@keyframes opacityIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +@keyframes fontSizeTitle { + 0% { + font-size: 1em; + } + 100% { + font-size: 2em; + } + +} + +.background-video { + position: relative; + height: 100vh; /* Full viewport height */ + width: 100%; /* Full width */ + overflow: hidden; +} + +.dark-container { + position:relative; + height: 100vh; + width: 100%; + overflow: hidden; + background-color: #000220; +} +.background-video video { + position: absolute; + width: 100%; + height: 100vh; + object-fit: cover; /* Ensures the video covers the container */ + z-index: -1; /* Places the video behind the content */ +} + +.content { + color: white; + position: absolute; + top: 50px; + left: 20px; + right: 20px; + z-index: 100; + text-align: center; + font-family: Arial, sans-serif; + padding: 20px; +} + +.text-announce { + margin-top: 15%; + font-size: 2em; + font-weight: bold; + animation: slideInUp 1s forwards, fontSizeTitle 1s forwards, opacityIn 1s forwards; +} + +.shooting_star { + position: absolute; + left: 10%; + top: 10%; + // width: 100px; + height: 2px; + background: linear-gradient(-45deg, rgb(243, 227, 6), rgba(0, 0, 255, 0)); + border-radius: 999px; + filter: drop-shadow(0 0 6px rgb(250, 253, 148)); + animation: + tail $shooting-time ease-in-out infinite, + shooting $shooting-time ease-in-out infinite; + + &::before { + content: ''; + position: absolute; + top: calc(50% - 1px); + right: 0; + // width: 30px; + height: 2px; + background: linear-gradient(-45deg, rgba(0, 0, 255, 0), rgba(95, 145, 255, 1), rgba(0, 0, 255, 0)); + transform: translateX(50%) rotateZ(45deg); + border-radius: 100%; + animation: shining $shooting-time ease-in-out infinite; + } + + &::after { + // CodePen Error + // @extend .shooting_star::before; + + content: ''; + position: absolute; + top: calc(50% - 1px); + right: 0; + // width: 30px; + height: 2px; + background: linear-gradient(-45deg, rgba(0, 0, 255, 0), rgba(95, 145, 255, 1), rgba(0, 0, 255, 0)); + transform: translateX(50%) rotateZ(45deg); + border-radius: 100%; + animation: shining $shooting-time ease-in-out infinite; + transform: translateX(50%) rotateZ(-45deg); + } + + @for $i from 1 through 20 { + &:nth-child(#{$i}) { + $delay: random(9999) + 0ms; + top: calc(50% - #{random(2000) - 200px}); + left: calc(50% - #{random(300) + 0px}); + animation-delay: $delay; + opacity: random(50) / 100 + 0.5; + + &::before, + &::after { + animation-delay: $delay; + } + } + } +} + +@keyframes tail { + 0% { + width: 0; + } + + 30% { + width: 100px; + } + + 100% { + width: 0; + } +} + +@keyframes shining { + 0% { + width: 0; + } + + 50% { + width: 30px; + } + + 100% { + width: 0; + } +} + +@keyframes shooting { + 0% { + transform: translateX(0); + } + + 100% { + transform: translateX(300px); + } +} + +@keyframes sky { + 0% { + transform: rotate(45deg); + } + + 100% { + transform: rotate(45 + 360deg); + } +} + + +@keyframes glow { + 0% { + text-shadow: 0 0 2px #f5f5f5, 0 0 10px #f5f5f5, 0 0 20px #ffc14d, 0 0 30px #fff1e0, 0 0 40px #ff4da6, 0 0 50px #ff4da6, 0 0 75px #ff4da6; + } + 100% { + text-shadow: 0 0 20px #ffffff, 0 0 20px #ff0080, 0 0 30px #ff0080, 0 0 40px #ff0080, 0 0 50px #ff0080, 0 0 75px #ff0080, 0 0 100px #ff0080; + } +} +.night { + position: relative; + width: 100%; + height: 100%; + transform: rotateZ(45deg); + animation: sky 100000ms linear infinite; +} + +.score { + background-color: #f8b12c; + border-radius: 50%; + padding: 10px; + text-shadow: 0 0 5px #f5f5f5, 0 0 10px #f5f5f5, 0 0 20px #ff4da6, 0 0 30px #ff4da6, 0 0 40px #ff4da6, 0 0 50px #ff4da6, 0 0 75px #ff4da6; + animation: glow 2s infinite alternate; +} \ No newline at end of file diff --git a/src/app/views/endgamepoints/endgamepoints.component.spec.ts b/src/app/views/endgamepoints/endgamepoints.component.spec.ts new file mode 100644 index 0000000..f4f8e6e --- /dev/null +++ b/src/app/views/endgamepoints/endgamepoints.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EndgamepointsComponent } from './endgamepoints.component'; + +describe('EndgamepointsComponent', () => { + let component: EndgamepointsComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [EndgamepointsComponent] + }); + fixture = TestBed.createComponent(EndgamepointsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/views/endgamepoints/endgamepoints.component.ts b/src/app/views/endgamepoints/endgamepoints.component.ts new file mode 100644 index 0000000..a7e7e05 --- /dev/null +++ b/src/app/views/endgamepoints/endgamepoints.component.ts @@ -0,0 +1,94 @@ +import {Component, OnInit} from '@angular/core'; +import {ApiService, EndgameResultsDto, FeatureFlagStateDto} from "../../services/api.service"; +import { + Subject, + firstValueFrom, + combineLatest, + merge +} from "rxjs"; +import { takeUntil} from "rxjs/operators"; +import {VoiceService} from "../../services/voice.service"; +import {animate, style, transition, trigger} from "@angular/animations"; +import {Participant} from "../../../types/participant"; +import {SharedMethods} from "../../shared/sharedmethods"; + +@Component({ + selector: 'app-endgamepoints', + templateUrl: './endgamepoints.component.html', + styleUrls: ['./endgamepoints.component.scss'], + animations: [ + trigger('slideOut', [ + transition(':leave', [ + animate( + '1300ms ease-in', + style({ transform: 'translateY(-100%)', opacity: 0 }) + ), + ]), + ])] +}) +export class EndgamepointsComponent implements OnInit{ + loaded = false; + useCssAnimation = false; + destroyed$ = new Subject(); + showInitialText = true; + showMaxAmountOfInvalidAnswers = false; + endgameResults: EndgameResultsDto | null; + participants: Participant[] = []; + maxAmountOfPenalties = false; + maxAmountOfRewards = false; + constructor(private apiService: ApiService, private voiceService: VoiceService) { + } + + + ngOnInit(): void { + const ff$ = this.apiService.getFeatureFlagState("EndgamePointsUseCssAnimation"); + // @ts-ignore + const results$ = this.apiService.getEndgameResults(); + combineLatest([results$, ff$]) + .pipe(takeUntil(this.destroyed$)) + .subscribe(([results,ff]) => { + const userData$ = []; + userData$.push( + this.apiService.getParticipant(results.maxInvalidAnswers.id), + this.apiService.getParticipant(results.maxPenalties.id), + this.apiService.getParticipant(results.maxRewards.id), + ); + merge(...userData$).pipe(takeUntil(this.destroyed$)).subscribe((r) => this.participants.push(r)); + this.useCssAnimation = ff.state; + this.endgameResults = results; + this.loaded = true; + this.playScene().then(() => {}); + console.log(results); + }); + } + + + async playScene() { + await firstValueFrom(this.voiceService.playAudio$(this.voiceService.getAudioUrl("Время наградить особо отличившихся!"))); + this.showInitialText = false; + await SharedMethods.sleep(1000); + this.showMaxAmountOfInvalidAnswers = true; + await SharedMethods.sleep(500); + await firstValueFrom(this.voiceService.playAudio$(this.voiceService.getAudioUrl(` За максимальное количество неверных ответов, плюс два очка получает ${this.endgameResults?.maxInvalidAnswers.name}`))); + this.showMaxAmountOfInvalidAnswers = false; + await SharedMethods.sleep(3000); + this.maxAmountOfPenalties = true; + await firstValueFrom(this.voiceService.playAudio$(this.voiceService.getAudioUrl(`За самое большое количество полученных наказаний плюс два очка получил ${this.endgameResults?.maxPenalties.name}`))); + await SharedMethods.sleep(3000) + this.maxAmountOfPenalties = false; + await firstValueFrom(this.voiceService.playAudio$(this.voiceService.getAudioUrl(`И чтобы сделать игру более справедливой, есть последняя номинация`))); + this.maxAmountOfRewards = true; + await firstValueFrom(this.voiceService.playAudio$(this.voiceService.getAudioUrl(`${this.endgameResults?.maxRewards.name} лишается двух очков за свой невероятный ум`))); + await SharedMethods.sleep(15000); + + } + + + getParticipant(id: number | undefined): Participant|null { + if(id) { + const p = this.participants.find(x => x.telegramId === id); + return p !== undefined ? p : null; + } + return null; + } +} diff --git a/src/app/views/finish/finish.component.html b/src/app/views/finish/finish.component.html index 6c9eb45..064cbb8 100644 --- a/src/app/views/finish/finish.component.html +++ b/src/app/views/finish/finish.component.html @@ -15,6 +15,6 @@
- +
diff --git a/src/app/views/onboarding/onboarding.component.html b/src/app/views/onboarding/onboarding.component.html index 11b6408..071a83c 100644 --- a/src/app/views/onboarding/onboarding.component.html +++ b/src/app/views/onboarding/onboarding.component.html @@ -22,11 +22,11 @@

Если тебе требуется пояснять эту карточку - то игра не для тебя, налей себе алкоголь и побольше.

-
- ... +
+ ...
-
Говнокарта
-

Можно подкинуть еще один вопрос игроку, который правильно ответил.

+
Поединок
+

Можно вызвать другого игрока на схватку 1 на 1 в мини-игре

@@ -54,7 +54,7 @@ ...
Заблокировать игрока
-

Запрещает игроку давать ответы в следующих двух раундах

+

Запрещает игроку давать ответы в случайном количестве раундов

diff --git a/src/app/views/onboarding/onboarding.component.ts b/src/app/views/onboarding/onboarding.component.ts index beee989..ced4203 100644 --- a/src/app/views/onboarding/onboarding.component.ts +++ b/src/app/views/onboarding/onboarding.component.ts @@ -33,7 +33,7 @@ interface RuleItem { export class OnboardingComponent implements OnInit, OnDestroy { @ViewChild('avoidPenaltyCard') private avoidPenaltyCardEl: ElementRef; @ViewChild('stolePrizeCard') private stolePrizeCardEl: ElementRef; - @ViewChild('shitCard') private shitCardEl: ElementRef; + @ViewChild('versusCard') private versusCardEl: ElementRef; @ViewChild('luckyCard') private luckyCardEl: ElementRef; @ViewChild('banPlayerCard') private banPlayerEl: ElementRef; @ViewChild('doubleTreasureCard') private doubleTreasureCardEl: ElementRef; @@ -42,30 +42,25 @@ export class OnboardingComponent implements OnInit, OnDestroy { { text: 'Вопросы и ответы будут отображаться на экране и в Боте Благодарения.' }, { text: 'Каждый игрок в начале игры имеет на руках 4 карты, набор карт определяется случайно. Описание карт ты найдешь ниже. После использования карты ты получаешь новую случайную карту.' }, { text: 'На разыгрывание карты время ограничено, примерно 10 секунд.' }, - { text: 'Задача игрока - ответить правильно и быстрее других.' }, - { text: 'Первый игрок, ответивший правильно, получает одно очко и шанс выиграть приз.' }, - { text: 'Приз??? Какой приз?', screpa: true, voice: 2 }, - { text: 'Я не думаю, что их мозгов хватит для получения призов', screpa: true, voice: 2 }, - { text: 'А ты вообще кто такая?', hideWithoutVoice: true }, - { text: 'Ах, да.. простите, забыла представиться, я - Скрепа по фамилии Духовная', screpa: true, voice: 2}, - { text: 'И на кой ты нам нужна?', hideWithoutVoice: true }, - { text: 'Я тут, чтобы нарушать ход игры, и вообще тебя не спрашивали. ', screpa: true, voice: 2}, - { text: '[Ладно, ]В общем - чем больше правильных ответов - тем больше призов '}, - { text: 'Первый игрок, ответивший неправильно, получает наказание, и мы переходим к следующему вопросу' }, + { text: 'Вы долго просили оптимизировать геймплей для медленных и глупых, и мы это сделали!'}, + { text: 'Задача игрока - ответить правильно и быстрее других, ну или хотя бы просто правильно в течение 20 секунд' }, + { text: 'Первый игрок, ответивший правильно, получает два очка' }, + { text: 'Все остальные, ответившие правильно, получают одно очко'}, + { text: 'Иногда за неправильные ответы игроки будут получать наказания' }, { text: 'Избежать наказания можно только с помощью соотвествуещей карты, данную карту ты можешь сыграть перед озвучиванием наказания', action: () => { this.shakeCard(this.avoidPenaltyCardEl); }}, { text: 'Карту "украсть приз" ты можешь сыграть в момент, когда кто-то собирается получить награду, но до момента того, как ты узнаешь, что это именно за приз', action: () => { this.shakeCard(this.stolePrizeCardEl); }}, - { text: '"Говно-карту" ты можешь разыграть в момент, когда кто-то ответил правильно, тем самым ты заставишь именно этого игрока ответить на один дополнительный вопрос. На одного игрока можно сыграть неограниченное количество этих карт', action: () => { - this.shakeCard(this.shitCardEl); + { text: 'Карту "Поединок" ты можешь разыграть в любой момент, чтобы вызвать игрока на дуэль', action: () => { + this.shakeCard(this.versusCardEl); }}, { text: '"Лаки карту" ты сможешь сыграть после своего правильного ответа, она увеличит твои шансы на получение приза', action: () => { this.shakeCard(this.luckyCardEl); }}, { - text: 'Карту бана можно сыграть на любого игрока (даже себя), чтобы отправить его на пару ходов во Владикавказ. Игрок не сможет участвовать в игре в течение двух раундов', + text: 'Карту бана можно сыграть на любого игрока (даже себя), чтобы отправить его на пару ходов во Владикавказ. Игрок не сможет участвовать в случайном количестве раундов', action: () => { this.shakeCard(this.banPlayerEl); } @@ -78,22 +73,10 @@ export class OnboardingComponent implements OnInit, OnDestroy { }, { text: 'Не торопись с ответами, игра идет до той поры, пока мы не разыграем все призы' }, { - text: 'Ах да, так как создатель игры, работает на Microsoft, мы добавили привычные вам баги, сейчас расскажу', - screpa: true, - voice: 2 + text: 'Ах да, так как создатель игры, работает на Microsoft, мы добавили привычные вам баги, но все их оставим в секрете', }, { - text: 'Если у вас нет вариантов ответа - перезайдите в бот или перезапустите телеграмм', - screpa: true, - voice: 2, - }, - { - text: 'Остальные баги будут сюрпризом! Но не забывайте громко кричать, когда что-то работает не так', - screpa: true, - voice: 2, - }, - { - text: 'Кажется правила закончились' + text: 'Кажется, правила закончились' } ]; @@ -114,11 +97,10 @@ export class OnboardingComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { this.destroyed$.complete(); - this.voiceSubscription.unsubscribe(); + this.voiceSubscription?.unsubscribe(); } shakeCard(card: ElementRef) { - console.log(`shake card`); this.renderer.addClass(card.nativeElement, 'shake'); this.renderer.addClass(card.nativeElement, 'zoom-in'); if(!this.allRulesAnnounced) { @@ -142,7 +124,6 @@ export class OnboardingComponent implements OnInit, OnDestroy { } stopShaking(card: ElementRef) { - console.log(`stop shacking`); this.renderer.removeClass(card.nativeElement, 'shake'); } @@ -174,7 +155,7 @@ export class OnboardingComponent implements OnInit, OnDestroy { this.allRulesAnnounced = true; this.voiceService.playAudio(getAudioPath(`Это все правила, надеюсь, все понятно. А если нет - сейчас Кирилл и Оксана вам все пояснят, ну и совсем для тупых - пустила по кругу правила на экране, - а если ты их не поймешь - то за Путина голосовать пойдешь (или за Грузинскую мечту) . Каждый правильный ответ отнимает у Путина год жизни, постарайтесь!`)); + а если ты их не поймешь - то очень жаль тебя глупенького`)); this.voiceService.audioEndedSubject.pipe(takeUntil(this.destroyed$),take(1)).subscribe(() => { setInterval(() => { this.playNextRule() }, 6000); this.currentRulePosition = 0 diff --git a/src/assets/cards/VersusCard.png b/src/assets/cards/VersusCard.png new file mode 100644 index 0000000..365aa36 Binary files /dev/null and b/src/assets/cards/VersusCard.png differ diff --git a/src/assets/endgame/1469-147538044.mp4 b/src/assets/endgame/1469-147538044.mp4 new file mode 100644 index 0000000..771f3d3 Binary files /dev/null and b/src/assets/endgame/1469-147538044.mp4 differ diff --git a/src/assets/endgame/48569-454825064.mp4 b/src/assets/endgame/48569-454825064.mp4 new file mode 100644 index 0000000..2199982 Binary files /dev/null and b/src/assets/endgame/48569-454825064.mp4 differ diff --git a/src/assets/endgame/energetic-bgm-242515.mp3 b/src/assets/endgame/energetic-bgm-242515.mp3 new file mode 100644 index 0000000..c7308e9 Binary files /dev/null and b/src/assets/endgame/energetic-bgm-242515.mp3 differ diff --git a/src/assets/finalcaption.mov b/src/assets/finalcaption.mov new file mode 100644 index 0000000..123a32c Binary files /dev/null and b/src/assets/finalcaption.mov differ diff --git a/src/assets/qr-code.svg b/src/assets/qr-code.svg index 3376fef..57d0e90 100644 --- a/src/assets/qr-code.svg +++ b/src/assets/qr-code.svg @@ -1,148 +1,1156 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/versus/cinematical-epic-loop-190906.mp3 b/src/assets/versus/cinematical-epic-loop-190906.mp3 new file mode 100644 index 0000000..0ae2c1b Binary files /dev/null and b/src/assets/versus/cinematical-epic-loop-190906.mp3 differ diff --git a/src/dicts/voice.dicts.ts b/src/dicts/voice.dicts.ts new file mode 100644 index 0000000..5a745ba --- /dev/null +++ b/src/dicts/voice.dicts.ts @@ -0,0 +1,4 @@ +export const VoiceDict = { + + +} \ No newline at end of file diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 3612073..b8d4a3e 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,3 +1,5 @@ export const environment = { - production: true + production: true, + API_URL: "https://thanksgiving2024.ngweb.io/api", + WEBSOCK_URL: "https://thanksgiving2024.ngweb.io" }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index f56ff47..3062ec4 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -3,7 +3,9 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false + production: false, + API_URL: "http://localhost:3000", + WEBSOCK_URL: "http://localhost:3000" }; /* diff --git a/src/types/questionresults.dto.ts b/src/types/questionresults.dto.ts new file mode 100644 index 0000000..a2969df --- /dev/null +++ b/src/types/questionresults.dto.ts @@ -0,0 +1,5 @@ +export class QuestionresultsDto { + user: number; + time: Date; + valid: boolean; +} \ No newline at end of file diff --git a/src/types/server-event.ts b/src/types/server-event.ts index ba41064..ac762bb 100644 --- a/src/types/server-event.ts +++ b/src/types/server-event.ts @@ -6,6 +6,8 @@ export enum QueueTypes { penalty = 'penalty', playExtraCard = 'play_extra_card', screpa = 'screpa', + showresults = 'show_results', + versus = 'versus', } export interface EventPhotosUpdated { @@ -50,6 +52,13 @@ export interface EventScoreChanged { newScore: number; } +export interface VersusBeginEvent { + player1: number; + player2: number; + player1name: string; + player2name: string; +} + export interface EventGameQueue { text?: string; target: number; @@ -86,5 +95,8 @@ export interface ServerEvent { | 'game_resumed' | 'notification' | 'user_property_changed' + | 'feature_flag_changed' + | 'begin_versus' + | 'end_versus' data: T } diff --git a/src/types/versus-item.ts b/src/types/versus-item.ts new file mode 100644 index 0000000..a5e3dfd --- /dev/null +++ b/src/types/versus-item.ts @@ -0,0 +1,6 @@ +export interface VersusItem { + text: string; + name: string; + completed: boolean; + description: string; +} \ No newline at end of file diff --git a/tsconfig.app.json b/tsconfig.app.json index 82d91dc..b120c1d 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -3,7 +3,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", - "types": [] + "types": ["node"] }, "files": [ "src/main.ts",