commit e1ec6b2db9a7d711d1b315e197e608a9fac4aa33 Author: dennisgunia Date: Thu Feb 25 23:11:23 2021 +0100 initial merge of both repos diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd5a8fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +UNO-Angular/node_modules +UNO-Angular/package-lock.json +UNO-Angular/dist +UNO-Backend/node_modules +UNO-Backend/package-lock.json +UNO-Backend/dist + diff --git a/UNO-Angular/.editorconfig b/UNO-Angular/.editorconfig new file mode 100644 index 0000000..59d9a3a --- /dev/null +++ b/UNO-Angular/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/UNO-Angular/.gitignore b/UNO-Angular/.gitignore new file mode 100644 index 0000000..86d943a --- /dev/null +++ b/UNO-Angular/.gitignore @@ -0,0 +1,46 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc +# Only exists if Bazel was run +/bazel-out + +# dependencies +/node_modules + +# profiling files +chrome-profiler-events*.json +speed-measure-plugin*.json + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db diff --git a/UNO-Angular/README.md b/UNO-Angular/README.md new file mode 100644 index 0000000..a712cb2 --- /dev/null +++ b/UNO-Angular/README.md @@ -0,0 +1,27 @@ +# Uno + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.0. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/UNO-Angular/angular.json b/UNO-Angular/angular.json new file mode 100644 index 0000000..6d6f51e --- /dev/null +++ b/UNO-Angular/angular.json @@ -0,0 +1,160 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "uno": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/uno", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "aot": true, + "assets": [ + "src/favicon.ico", + "src/assets/felt.jpg", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css", + "src/styles.scss", + "node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss", + "node_modules/@fortawesome/fontawesome-free/scss/solid.scss", + "node_modules/@fortawesome/fontawesome-free/scss/regular.scss", + "node_modules/@fortawesome/fontawesome-free/scss/brands.scss", + "node_modules/angular-bootstrap-md/assets/scss/bootstrap/bootstrap.scss", + "node_modules/angular-bootstrap-md/assets/scss/mdb.scss", + "node_modules/animate.css/animate.css" + ], + "scripts": [ + "node_modules/chart.js/dist/Chart.js", + "node_modules/hammerjs/hammer.min.js", + "node_modules/jquery/dist/jquery.js", + "node_modules/bootstrap/dist/js/bootstrap.js" + ] + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb", + "maximumError": "10kb" + } + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "uno:build" + }, + "configurations": { + "production": { + "browserTarget": "uno:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "uno:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "assets": [ + "src/favicon.ico", + "src/assets/felt.jpg", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css", + "src/styles.scss", + "node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss", + "node_modules/@fortawesome/fontawesome-free/scss/solid.scss", + "node_modules/@fortawesome/fontawesome-free/scss/regular.scss", + "node_modules/@fortawesome/fontawesome-free/scss/brands.scss", + "node_modules/angular-bootstrap-md/assets/scss/bootstrap/bootstrap.scss", + "node_modules/angular-bootstrap-md/assets/scss/mdb.scss", + "node_modules/animate.css/animate.css" + ], + "scripts": [ + "node_modules/chart.js/dist/Chart.js", + "node_modules/hammerjs/hammer.min.js", + "node_modules/jquery/dist/jquery.js", + "node_modules/bootstrap/dist/js/bootstrap.js" + ] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "tsconfig.app.json", + "tsconfig.spec.json", + "e2e/tsconfig.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + }, + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "uno:serve" + }, + "configurations": { + "production": { + "devServerTarget": "uno:serve:production" + } + } + } + } + } + }, + "defaultProject": "uno", + "cli": { + "analytics": false + } +} \ No newline at end of file diff --git a/UNO-Angular/browserslist b/UNO-Angular/browserslist new file mode 100644 index 0000000..8084853 --- /dev/null +++ b/UNO-Angular/browserslist @@ -0,0 +1,12 @@ +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries + +# You can see what browsers were selected by your queries by running: +# npx browserslist + +> 0.5% +last 2 versions +Firefox ESR +not dead +not IE 9-11 # For IE 9-11 support, remove 'not'. \ No newline at end of file diff --git a/UNO-Angular/dockerfile b/UNO-Angular/dockerfile new file mode 100644 index 0000000..7b83ab8 --- /dev/null +++ b/UNO-Angular/dockerfile @@ -0,0 +1,41 @@ +#build angular base build containeer +FROM node:12-alpine AS build_base +WORKDIR /usr/src/app +RUN apk add --no-cache git + + +#build project specific container containing modules +FROM build_base as build_base_project +COPY ./package.json /usr/src/app/package.json +COPY ./package-lock.json /usr/src/app/package-lock.json + +RUN npm install + +# create build container and build ng project +FROM build_base_project as build +ARG CI_COMMIT_BRANCH +ARG CI_COMMIT_TAG +ARG CI_COMMIT_SHA +ENV CI_COMMIT_BRANCH=$CI_COMMIT_BRANCH +ENV CI_COMMIT_TAG=$CI_COMMIT_TAG +ENV CI_COMMIT_SHA=$CI_COMMIT_SHA +COPY ./ /usr/src/app +RUN npm run build-prod + +#build nginx container +FROM nginx + +RUN rm /etc/nginx/conf.d/default.conf + + +RUN chgrp -R root /var/cache/nginx /var/run /var/log/nginx && \ + chmod -R 770 /var/cache/nginx /var/run /var/log/nginx + +USER root + +COPY --from=build /usr/src/app/dist/uno /usr/share/nginx/html +COPY ./nginx.conf /etc/nginx/nginx.conf + +EXPOSE 80 + + diff --git a/UNO-Angular/e2e/protractor.conf.js b/UNO-Angular/e2e/protractor.conf.js new file mode 100644 index 0000000..7c798cf --- /dev/null +++ b/UNO-Angular/e2e/protractor.conf.js @@ -0,0 +1,32 @@ +// @ts-check +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +/** + * @type { import("protractor").Config } + */ +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './src/**/*.e2e-spec.ts' + ], + capabilities: { + browserName: 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + onPrepare() { + require('ts-node').register({ + project: require('path').join(__dirname, './tsconfig.json') + }); + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; \ No newline at end of file diff --git a/UNO-Angular/e2e/src/app.e2e-spec.ts b/UNO-Angular/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000..d1b23d8 --- /dev/null +++ b/UNO-Angular/e2e/src/app.e2e-spec.ts @@ -0,0 +1,23 @@ +import { AppPage } from './app.po'; +import { browser, logging } from 'protractor'; + +describe('workspace-project App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', () => { + page.navigateTo(); + expect(page.getTitleText()).toEqual('uno app is running!'); + }); + + afterEach(async () => { + // Assert that there are no errors emitted from the browser + const logs = await browser.manage().logs().get(logging.Type.BROWSER); + expect(logs).not.toContain(jasmine.objectContaining({ + level: logging.Level.SEVERE, + } as logging.Entry)); + }); +}); diff --git a/UNO-Angular/e2e/src/app.po.ts b/UNO-Angular/e2e/src/app.po.ts new file mode 100644 index 0000000..b68475e --- /dev/null +++ b/UNO-Angular/e2e/src/app.po.ts @@ -0,0 +1,11 @@ +import { browser, by, element } from 'protractor'; + +export class AppPage { + navigateTo(): Promise { + return browser.get(browser.baseUrl) as Promise; + } + + getTitleText(): Promise { + return element(by.css('app-root .content span')).getText() as Promise; + } +} diff --git a/UNO-Angular/e2e/tsconfig.json b/UNO-Angular/e2e/tsconfig.json new file mode 100644 index 0000000..39b800f --- /dev/null +++ b/UNO-Angular/e2e/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/e2e", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "jasminewd2", + "node" + ] + } +} diff --git a/UNO-Angular/karma.conf.js b/UNO-Angular/karma.conf.js new file mode 100644 index 0000000..b51f949 --- /dev/null +++ b/UNO-Angular/karma.conf.js @@ -0,0 +1,32 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + coverageIstanbulReporter: { + dir: require('path').join(__dirname, './coverage/uno'), + reports: ['html', 'lcovonly', 'text-summary'], + fixWebpackSourcePaths: true + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + restartOnFileChange: true + }); +}; diff --git a/UNO-Angular/nginx.conf b/UNO-Angular/nginx.conf new file mode 100644 index 0000000..3e089df --- /dev/null +++ b/UNO-Angular/nginx.conf @@ -0,0 +1,60 @@ +user root; + +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include mime.types; + keepalive_timeout 65; + sendfile on; + + server { + #security + server_tokens off; + add_header X-XSS-Protection "1; mode=block"; + add_header Access-Control-Allow-Origin cdn.dennisgunia.de; + add_header X-Robots-Tag none; + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + add_header Referrer-Policy no-referrer; + add_header Cache-Control no-cache; + default_type text/html; + gzip on; + gzip_static on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + expires -1; + } + + location /unogame { + resolver 127.0.0.11 ipv6=off; + #proxy_set_header X-Real-IP $remote_addr; + proxy_read_timeout 300s; + proxy_connect_timeout 75s; + proxy_pass http://backend:3000; + + } + location /stream { + resolver 127.0.0.11 ipv6=off; + proxy_pass http://backend:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + } + + listen 80; + + } +} + diff --git a/UNO-Angular/package.json b/UNO-Angular/package.json new file mode 100644 index 0000000..7eb11ac --- /dev/null +++ b/UNO-Angular/package.json @@ -0,0 +1,67 @@ +{ + "name": "uno", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e", + "build-prod": "ng build --prod --sourceMap" + }, + "private": true, + "dependencies": { + "@angular/animations": "~9.1.0", + "@angular/cdk": "^9.2.0", + "@angular/common": "~9.1.0", + "@angular/compiler": "~9.1.0", + "@angular/core": "~9.1.0", + "@angular/forms": "~9.1.0", + "@angular/localize": "^9.1.0", + "@angular/material": "^9.2.0", + "@angular/platform-browser": "~9.1.0", + "@angular/platform-browser-dynamic": "~9.1.0", + "@angular/router": "~9.1.0", + "@fortawesome/fontawesome-free": "^5.13.0", + "@ng-bootstrap/ng-bootstrap": "^6.0.2", + "@stomp/stompjs": "^5.4.4", + "@types/chart.js": "^2.9.16", + "@types/socket.io": "^2.1.4", + "@types/sockjs-client": "^1.1.1", + "@types/stompjs": "^2.3.4", + "angular-bootstrap-md": "^9.0.0", + "animate.css": "^3.7.2", + "bootstrap": "^4.4.1", + "chart.js": "^2.5.0", + "hammerjs": "^2.0.8", + "jquery": "^3.4.1", + "net": "^1.0.2", + "rxjs": "~6.5.4", + "sockjs-client": "^1.4.0", + "stompjs": "^2.3.3", + "tslib": "^1.10.0", + "zone.js": "~0.10.2" + }, + "devDependencies": { + "@angular-devkit/build-angular": "~0.901.0", + "@angular/cli": "~9.1.0", + "@angular/compiler-cli": "~9.1.0", + "@angular/language-service": "~9.1.0", + "@types/node": "^12.11.1", + "@types/jasmine": "~3.5.0", + "@types/jasminewd2": "~2.0.3", + "codelyzer": "^5.1.2", + "jasmine-core": "~3.5.0", + "jasmine-spec-reporter": "~4.2.1", + "karma": "~4.4.1", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage-istanbul-reporter": "~2.1.0", + "karma-jasmine": "~3.0.1", + "karma-jasmine-html-reporter": "^1.4.2", + "protractor": "~5.4.3", + "ts-node": "~8.3.0", + "tslint": "~6.1.0", + "typescript": "~3.8.3" + } +} diff --git a/UNO-Angular/proxy.json b/UNO-Angular/proxy.json new file mode 100644 index 0000000..87dbedc --- /dev/null +++ b/UNO-Angular/proxy.json @@ -0,0 +1,18 @@ +{ + "/unogame": { + "target": { + "host": "127.0.0.1", + "protocol": "http:", + "port": 3000 + }, + "secure": false, + "changeOrigin": true, + "logLevel": "info" + }, + "/stream/*": { + "target": "http://127.0.0.1:3000", + "secure": false, + "ws": true + } + +} diff --git a/UNO-Angular/src/app/app-routing.module.ts b/UNO-Angular/src/app/app-routing.module.ts new file mode 100644 index 0000000..63bff08 --- /dev/null +++ b/UNO-Angular/src/app/app-routing.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { StartComponent } from './start/start.component'; +import { GameComponent } from './game/game.component'; +import { BrowserComponent } from './browser/browser.component'; + + +const routes: Routes = [ + { path: 'start', component: StartComponent }, + { path: 'game', component: GameComponent }, + { path: 'browser', component: BrowserComponent }, + { + path: '', + redirectTo: '/start', + pathMatch: 'full' + }, +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/UNO-Angular/src/app/app.component.html b/UNO-Angular/src/app/app.component.html new file mode 100644 index 0000000..8845ef5 --- /dev/null +++ b/UNO-Angular/src/app/app.component.html @@ -0,0 +1,7 @@ + + + + + diff --git a/UNO-Angular/src/app/app.component.scss b/UNO-Angular/src/app/app.component.scss new file mode 100644 index 0000000..f2176b3 --- /dev/null +++ b/UNO-Angular/src/app/app.component.scss @@ -0,0 +1,3 @@ +.navbar{ + opacity: 0.7; +} \ No newline at end of file diff --git a/UNO-Angular/src/app/app.component.ts b/UNO-Angular/src/app/app.component.ts new file mode 100644 index 0000000..be9a1e2 --- /dev/null +++ b/UNO-Angular/src/app/app.component.ts @@ -0,0 +1,75 @@ +import { Component } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; +import { trigger, transition, style, query, animateChild, animate, group } from '@angular/animations'; + + + + +const slideInAnimation = + trigger('routeAnimations', [ + transition('HomePage <=> AboutPage', [ + style({ position: 'relative' }), + query(':enter, :leave', [ + style({ + position: 'absolute', + top: 0, + left: 0, + width: '100%' + }) + ]), + query(':enter', [ + style({ left: '-100%'}) + ]), + query(':leave', animateChild()), + group([ + query(':leave', [ + animate('300ms ease-out', style({ left: '100%'})) + ]), + query(':enter', [ + animate('300ms ease-out', style({ left: '0%'})) + ]) + ]), + query(':enter', animateChild()), + ]), + transition('* <=> FilterPage', [ + style({ position: 'relative' }), + query(':enter, :leave', [ + style({ + position: 'absolute', + top: 0, + left: 0, + width: '100%' + }) + ]), + query(':enter', [ + style({ left: '-100%'}) + ]), + query(':leave', animateChild()), + group([ + query(':leave', [ + animate('200ms ease-out', style({ left: '100%'})) + ]), + query(':enter', [ + animate('300ms ease-out', style({ left: '0%'})) + ]) + ]), + query(':enter', animateChild()), + ]) + ]); + + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'], + animations: [ + slideInAnimation + // animation triggers go here + ] +}) +export class AppComponent { + title = 'uno'; + prepareRoute(outlet: RouterOutlet) { + return outlet && outlet.activatedRouteData && outlet.activatedRouteData['animation']; + } +} diff --git a/UNO-Angular/src/app/app.module.ts b/UNO-Angular/src/app/app.module.ts new file mode 100644 index 0000000..3146376 --- /dev/null +++ b/UNO-Angular/src/app/app.module.ts @@ -0,0 +1,38 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; + +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { StartComponent } from './start/start.component'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MDBBootstrapModule } from 'angular-bootstrap-md'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { GameComponent } from './game/game.component'; +import { BrowserComponent } from './browser/browser.component'; + + +@NgModule({ + declarations: [ + AppComponent, + StartComponent, + GameComponent, + BrowserComponent + ], + imports: [ + BrowserModule, + AppRoutingModule, + BrowserAnimationsModule, + AppRoutingModule, + NgbModule, + MDBBootstrapModule.forRoot(), + FormsModule, + HttpClientModule, + ReactiveFormsModule, + + ], + providers: [{provide: Window, useValue: window},], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/UNO-Angular/src/app/browser/browser.component.html b/UNO-Angular/src/app/browser/browser.component.html new file mode 100644 index 0000000..2c000fa --- /dev/null +++ b/UNO-Angular/src/app/browser/browser.component.html @@ -0,0 +1,107 @@ +
+
+
+

UNO Sitzungen - {{nick}}

+
+ + + + + +
+
    +
  • +
    +
    +
    + {{lobby.properties.serverName}} + (Im Spiel) + (in Lobby) +
    +
    + {{lobby.players}} Spieler +
    +
    +
      +
    • {{lobby.properties.startcards}} Karten zu Spielbeginn
    • +
    • Kumulieren
    • +
    • Jump-In
    • +
    • Doppeln
    • +
    • Pennen
    • +
    • Seven-O (Sieben-Null)
    • + +
    +
    +
    + +
    +
    +
    +
  • + +
+
    +
  • + Es gibt keine aktiven Sitzungen. +
  • + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/UNO-Angular/src/app/browser/browser.component.scss b/UNO-Angular/src/app/browser/browser.component.scss new file mode 100644 index 0000000..53488b4 --- /dev/null +++ b/UNO-Angular/src/app/browser/browser.component.scss @@ -0,0 +1,11 @@ +.lobby-card{ + margin: 0 auto; + margin-top: 1rem; + width: 80%; +} + +.btn-align { + padding: 6px 12px; + line-height: 1.42857143; + vertical-align: middle; +} \ No newline at end of file diff --git a/UNO-Angular/src/app/browser/browser.component.spec.ts b/UNO-Angular/src/app/browser/browser.component.spec.ts new file mode 100644 index 0000000..b55419e --- /dev/null +++ b/UNO-Angular/src/app/browser/browser.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BrowserComponent } from './browser.component'; + +describe('BrowserComponent', () => { + let component: BrowserComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ BrowserComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BrowserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/UNO-Angular/src/app/browser/browser.component.ts b/UNO-Angular/src/app/browser/browser.component.ts new file mode 100644 index 0000000..3599fa4 --- /dev/null +++ b/UNO-Angular/src/app/browser/browser.component.ts @@ -0,0 +1,105 @@ +import { Component, OnInit } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Router } from '@angular/router'; +import { FormBuilder,ReactiveFormsModule, FormControl, Validators } from '@angular/forms'; + +export interface UnoLobbyProperties { + serverName: string; + startcards: number; + +} + +export interface UnoLobbyElement { + id: string; + properties?: UnoLobbyProperties; + status?: boolean; + players?: number; + +} + +@Component({ + selector: 'app-browser', + templateUrl: './browser.component.html', + styleUrls: ['./browser.component.scss'] +}) +export class BrowserComponent implements OnInit { + + constructor( + private httpClient: HttpClient, + private router: Router, + private formBuilder: FormBuilder, + ) { + this.lobbies = [] + } + + createForm; + nick: string; + + ngOnInit(): void { + + this.createForm = this.formBuilder.group({ + serverName: ['Neue Sitzung', [Validators.required, Validators.minLength(3), Validators.maxLength(20)]], + startcards: [7, [Validators.required, Validators.min(2), Validators.max(20)]], + sw_stacking: [false, [Validators.required]], + sw_jumpin: [false, [Validators.required]], + sw_double: [false, [Validators.required]], + sw_sleep: [false, [Validators.required]], + sw_seveno: [false, [Validators.required]], + }); + + + this.nick = localStorage.getItem('username') + if (!this.nick){ + this.router.navigate(['/start']); + } + this.loadLobbies(); + } + + lobbies: UnoLobbyElement[]; + + loadLobbies(){ + this.httpClient.get("/unogame/lobbies").subscribe(result => { + const cres: any = result; + if (cres.success) { + console.log(cres.data) + this.lobbies = cres.data ? cres.data : []; + } + }); + } + + prepareModal(){ + + } + + createLobby(){ + + console.log("create" , this.createForm.value); + if(this.createForm.valid){ + document.getElementById("closeModalButton").click(); + this.httpClient.post("/unogame/create",this.createForm.value).subscribe(result => { + const cres: any = result; + if (cres.success) { + console.log(cres.data.id) + this.joinGame({id: cres.data.id}) + } + }); + } + + } + + joinGame(lobby: UnoLobbyElement){ + this.httpClient.post("/unogame/join", { + nick: localStorage.getItem('username'), + lid: lobby.id + }).subscribe(result => { + const cres: any = result; + if (cres.success) { + console.log(cres.data) + localStorage.setItem('uid', cres.data.userId); + localStorage.setItem('session', lobby.id); + this.router.navigate(['/game']); + } + }); + } + +} diff --git a/UNO-Angular/src/app/game-state.service.ts b/UNO-Angular/src/app/game-state.service.ts new file mode 100644 index 0000000..80f97cf --- /dev/null +++ b/UNO-Angular/src/app/game-state.service.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) + +export class GameStateService { + + constructor() { } +} diff --git a/UNO-Angular/src/app/game/game.component.html b/UNO-Angular/src/app/game/game.component.html new file mode 100644 index 0000000..7b2fd64 --- /dev/null +++ b/UNO-Angular/src/app/game/game.component.html @@ -0,0 +1,202 @@ +
+ + + + +
+ +
+
+
+

{{timer}}

+
+ +
+
+ {{currentVote.questions[0].question}} +
+
    + +
  • +
    {{opt}} +
  • + +
+
+ +
+

{{currentPlayer.nickname}} ist am Zug

+

Du bist am Zug

+

Aktuell wird Blau gespielt

+

Aktuell wird Grün gespielt

+

Aktuell wird Rot gespielt

+

Aktuell wird Gelb gespielt

+

{{currentPlayer.nickname}} sucht sich eine Farbe aus

+
+
+

Warte bis alle Spieler bereit sind

+
+
+
+ + +

+ + +
+
+
+ +
+ {{bannerText}} +
+
+ {{bannerText}} +
+
+
+ + + + +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/UNO-Angular/src/app/game/game.component.scss b/UNO-Angular/src/app/game/game.component.scss new file mode 100644 index 0000000..6f1a66e --- /dev/null +++ b/UNO-Angular/src/app/game/game.component.scss @@ -0,0 +1,264 @@ + + +.playerlist { + background-color: rgb(247, 247, 249); + border-right: solid 1px rgb(128, 128, 128); +} + +.wrapper { + display: flex; + width: 100%; + align-items: stretch; +} + +#sidebar { + min-width: 250px; + max-width: 250px; + background-color: rgba(247, 247, 249,0.3); +} + +.fill { + top: 46px; + left: 0; + right: 0; + bottom: 0; + position: absolute; + width: auto; + height: auto; +} + +.player { + margin: 0.5rem; + padding: 0.5rem; + +} + +.alignleft { + float: left; +} + +.alignright { + float: right; +} + +.lessspace { + margin-bottom: 0px; +} + +.playername { + font-weight:500; +} + +.player:hover { + background-color: #DCDCDC; +} + +.footer { + position: fixed; + left: 0; + bottom: 0; + width: 249px; + border-top: solid 1px rgb(128, 128, 128); + text-align: center; + padding-top: 0.5rem; +} + + +.footer2 { + position: fixed; + width: 35%; + bottom: 15rem; + right: 0; + //top: 46px; + height: 200px; + text-align: center; + padding: 0.5rem; + text-shadow: 0px 0px 12px #000000; + font-size: 1rem; + text-align: right; + color: white; + overflow-y: hidden; + overflow-x: hidden; + //opacity: 0.75; +} + + +.footer3 { + position: fixed; + left: 250px; + bottom: 0rem; + right: 0; + text-align: left; + border-top: solid 5px #2e1a12; + padding: 0.5rem; + overflow-y: scroll; + overflow-x: hidden; + height: 15rem; + background-image: url('/assets/felt.jpg'); + box-shadow: inset 0 0 10px #000000; +} + +.btn_btm { + margin-bottom: 0.5rem; + margin-left: 0.5rem; + margin-right: 0.5rem; + margin-top: 0; + width: calc(100% - 1rem); +} + +.uno-card { + height: 150px; + width: 100px; + float: left; + margin-right: 0.5rem; + background: white; + border: 0; + border-radius: 10px; + box-shadow: 0 0 10px #000000; +} + +.uno-card:hover { + transform: scale(1.1); /* (150% zoom - Note: if the zoom is too large, it will go outside of the viewport) */ +} + +.gamefield { + position: fixed; + left: 250px; + bottom: 15rem; + right: 0; + text-align: left; + padding: 0.5rem; + top: 46px; + background-image: url('/assets/felt.jpg'); + box-shadow: inset 0 0 10px #000000; +} + +.text-header { + left: 250px; + top: 46px; + right: 0; + text-align: center; + + color: snow; + text-shadow: 0px 0px 7px #000000; +} + +.text-header-current { + padding-top: 1.5rem; + font-size: 4rem; + font-weight: 600; +} +.text-header-detail { + padding-top: 1.0rem; + font-size: 2rem; +} + +.play-field { + position: fixed; + + left: 250px; + top: 180px; + right: 0; + bottom: 20rem; + text-align:center; +} + +.cards-center { + display: inline-block; + text-align: center +} + +.btn-skip { + margin-top:3rem; +} + + .modal-content { + display: inline-block; + text-align: center +} + + +.notify-banner { + position: fixed; + left: 250px; + bottom: 21rem; + right: 0; + text-align: center; + padding: 0.5rem; + color: snow; + text-shadow: 0px 0px 7px #000000; + font-size: 2rem; +} + +.uno-overlap { + margin-bottom: -100px; +} +:host span.chat-line-date { + padding-right: 2rem; + color:red; +} + +.chat-line-msg{ + +} + +.votebox { + //background-color: rgba($color: red, $alpha: 0.5); + position: fixed; + left: 260px; + bottom: calc(15rem + 10px ); + //padding: 0.5rem; + //color: snow; +} + +.vote-option{ + transition: 0.4s; + cursor: pointer; +} + +.vote-option:hover{ + background-color: lightgrey; + +} + +.progress{ + position:absolute; left:0px; top:0; + background-color: rgba($color: red, $alpha: .2); + width: 0%; + height: 100%; + border-radius: 0px; + transition: 1s; +} +.vote-opt-b{ + position:absolute; right: -27px; top:-18px; +} + +.rule { + display: block; +} + +.countdown { + position:absolute; left: 10px; top:10px; + padding: 0.5rem; + padding-bottom: 0; + font-size: 3rem; +} + + +.double-card{ + font-size: 3.5rem; + padding: 0; + text-shadow: 0px 0px 4px #FFF; +} + +@media only screen and (min-width: 42em) and (max-width: 64em) { + /* min-width 672px / max-width 1024px */ + /* hier vermuten wir Tabletts */ + +} + +@media only screen and (min-width: 42em) and (max-width: 42em) { + /* min-width 672px / max-width 1024px */ + /* hier vermuten wir Handy */ + +} \ No newline at end of file diff --git a/UNO-Angular/src/app/game/game.component.ts b/UNO-Angular/src/app/game/game.component.ts new file mode 100644 index 0000000..399aa79 --- /dev/null +++ b/UNO-Angular/src/app/game/game.component.ts @@ -0,0 +1,460 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Router } from '@angular/router'; +import { trigger, state, style, transition, animate } from '@angular/animations'; +import { Title } from '@angular/platform-browser'; +import { webSocket } from "rxjs/webSocket"; + +interface UnoCard { + id: number; + number: string; + color: string; + tx: string; +} + + +@Component({ + selector: 'app-game', + templateUrl: './game.component.html', + styleUrls: ['./game.component.scss'], + animations: [ + // the fade-in/fade-out animation. + trigger('simpleFadeAnimation', [ + + // the "in" style determines the "resting" state of the element when it is visible. + state('in', style({ opacity: 1 })), + + // fade in when created. this could also be written as transition('void => *') + transition(':enter', [ + style({ opacity: 0 }), + animate(150) + ]), + + // fade out when destroyed. this could also be written as transition('void => *') + transition(':leave', + animate(300, style({ opacity: 0 }))) + ]), + trigger('flyInOut', [ + state('in', style({ transform: 'translateX(0)' })), + transition('void => *', [ + style({ transform: 'translateX(100%)' }), + animate(100) + ]), + transition('* => void', [ + animate(100, style({ opacity: 0})) + ]) + ]) + ] +}) + +export class GameComponent implements OnInit { + + pulled: boolean = false; + nextPlayerId: string = ''; + conn; + myLastId: number = -1; + + //new objects + playerList: any = []; + winnerList: any = []; + topCard: any = {}; + currentPlayer: any = {}; + lastPlayer: any = {}; + direction: number; + gameStarted: boolean = false; + thisPlayer: any = {}; + handCards: UnoCard[] = []; + + bannerText: string = "test"; + bannerShow: boolean = false; + bannerError: boolean = false; + bannerTimer; + + chatText = ""; + selectedCard: UnoCard; + doubleCard: UnoCard; + + currentVote: any = undefined; + + options:any; + + timer: number = -1; + + constructor( + private httpClient: HttpClient, + private router: Router, + private titleService: Title, + @Inject(Window) private _window: Window + ) { + this.conn = webSocket(`ws${location.origin.replace(/http/,'')}/stream/uno.io`); + } + + ngOnInit(): void { + setInterval(() => { + this.conn.next({action: 'heartbeat'}); + console.log("players", this.playerList) + }, 4000); + this.openSocket(); + } + + private showBanner(message, type) { + if (this.bannerTimer) { + clearTimeout(this.bannerTimer); + } + this.bannerError = type; + this.bannerText = message; + this.bannerShow = true; + + this.bannerTimer = setTimeout(() => { + this.bannerShow = false; + }, 2000) + } + + private openSocket(){ + + + this.conn.subscribe( + msg => this.processMessage(msg), // Called whenever there is a message from the server. + err => { + console.log(err) + this.router.navigate(['/browser']) + }, // Called if at any point WebSocket API signals some kind of error. + () => this.router.navigate(['/browser']) // Called when connection is closed (for whatever reason). + ); + + this.conn.next({ + action: 'register', + id: localStorage.getItem('uid'), + lobby: localStorage.getItem('session'), + }); + this.conn.next({action: 'initData'}); + //this.conn.emit('new-message', "RAS@" + localStorage.getItem('uid')); + } + + private processMessage(msg){ + if (msg.notify){ this.showBanner(msg.notify,!msg.warning); return; } + if (msg.player){ this.processPlayerList(msg); return; } + if (msg.chat){ this.chatText += msg.chat; return; } + if (msg.initClientData){this.processInitClient(msg); return; } + if (msg.kick){ + this.conn.complete(); + this.router.navigate(['/browser']); + } + if (msg.gameUpdate){this.processGameUpdate(msg); return;} + if (msg.hand){this.processHand(msg); return; } + if (msg.vote){this.startVote(msg); return; } + if (msg.voteres){this.updateVote(msg); return; } + if (msg.endvote){this.stopVote(); return; } + if (msg.timer || msg.timer == 0){this.updateTimer(msg); return; } + if (msg.sound){this.playSound(msg); return; } + if (msg.changprop){ + if (msg.changprop == 'canskip'){ + this.pulled = true; // kann jetzt uno sagen + } + } + } + //new update functions + + private playSound(msg){ + let audio = new Audio(); + audio.src = msg.sound; + audio.load(); + audio.play(); + } + + private updateTimer(msg){ + if (msg.timer >= 0){ + let audio = new Audio(); + audio.src = "../assets/tick.wav"; + audio.load(); + audio.play(); + } + this.timer = msg.timer; + } + + private startVote(msg){ + this.currentVote = { + ...msg, + voted: false, + all: 0, + timer: setTimeout(() => this.stopVote(),msg.time) + } + + console.log("startvote",this.currentVote ) + } + + private stopVote(){ + clearTimeout(this.currentVote.timer) + this.currentVote = undefined; + console.log("stopvote",this.currentVote ) + } + + private updateVote(msg){ + this.currentVote.questions[0].counter = msg.voteres[0].counter; + this.currentVote.all = msg.voteres[0].counter.reduce((a, b) => a + b, 0) + console.log("updatevote",this.currentVote ) + + } + + public vote(id){ + console.log("vote") + this.conn.next({vote: id + 1}); + + } + + private processPlayerList(msg){ + if(msg.player == 'modify'){ + let pfound: boolean= false; + //search winner + this.winnerList.forEach((player, ix) => { + if(player.userId == msg.data.userId){ + pfound = true; + this.winnerList[ix] = msg.data; + console.log("update winner",msg.data) + } + }); + if (!pfound){ + this.playerList.forEach((player, ix) => { + if(player.userId == msg.data.userId){ + pfound = true; + this.playerList[ix] = msg.data; + console.log("update player",msg.data) + } + }); + } + if (!pfound){ + console.log("add player to list",msg.data ) + this.playerList.push(msg.data); + } + }else if(msg.player == 'remove'){ + this.playerList = this.playerList.filter(el => el.userId != msg.data.userId); + console.log("remove player from list",msg.data ) + }else if(msg.player == 'winners'){ + this.winnerList = msg.data + }else if(msg.player == 'all'){ + this.playerList = msg.players + this.winnerList = msg.winners + + } + this.processPlayerUpdate(); + this.playerChangeEvent(); + } + + private processHand(msg){ + if(msg.hand == 'add'){ + this.handCards.push(msg.card) + let audio = new Audio(); + audio.src = "../assets/cardSlide7.wav"; + audio.load(); + audio.play(); + }else if(msg.hand == 'remove'){ + this.handCards = this.handCards.filter(el => el.id != msg.card.id); + }else if(msg.hand == 'all'){ + this.handCards = msg.cards; + } + } + + private processInitClient(msg){ + this.playerList = msg.initClientData.players; + this.winnerList = msg.initClientData.winners; + //karten + this.topCard = msg.initClientData.cardsPlayed[msg.initClientData.cardsPlayed.length -1 ]; + for (let pi = 0; pi < this.playerList.length; pi++) { + if (this.playerList[pi].userId == msg.initClientData.currentPlayer) { + this.currentPlayer = this.playerList[pi]; + } + } + this.handCards = msg.initClientData.hand; + this.direction = msg.initClientData.direction; + this.updateCardColor(); + this.processPlayerUpdate(); + this.playerChangeEvent(); + this.gameStarted = msg.initClientData.started; + this.options = msg.initClientData.extraRules; + document.getElementById("open_rules_modal").click(); + + } + + private processGameUpdate(msg){ + switch(msg.gameUpdate){ + case 'playedStack': + this.topCard = msg.card[msg.card.length -1 ]; + this.updateCardColor(); + let audio = new Audio(); + audio.src = "../assets/cardPlace1.wav"; + audio.load(); + audio.play(); + break; + case 'currentPlayer': + for (let pi = 0; pi < this.playerList.length; pi++) { + if (this.playerList[pi].userId == msg.currentplayer) { + this.currentPlayer = this.playerList[pi]; + } + } + this.direction = msg.direction; + this.playerChangeEvent(); + break; + case 'status': + this.gameStarted = msg.started; + this.processPlayerUpdate(); + this.playerChangeEvent(); + break; + } + } + + //helper functions + private updateCardColor(){ + if (this.topCard){ + if (this.topCard.number == 'wild') { + switch (this.topCard.color) { + case 'blue': this.topCard.tx = "/assets/cards/311.png"; break; + case 'green': this.topCard.tx = "/assets/cards/321.png"; break; + case 'red': this.topCard.tx = "/assets/cards/331.png"; break; + case 'yellow': this.topCard.tx = "/assets/cards/341.png"; break; + } + } + if (this.topCard.number == 'wild+4') { + switch (this.topCard.color) { + case 'blue': this.topCard.tx = "/assets/cards/312.png"; break; + case 'green': this.topCard.tx = "/assets/cards/322.png"; break; + case 'red': this.topCard.tx = "/assets/cards/332.png"; break; + case 'yellow': this.topCard.tx = "/assets/cards/342.png"; break; + } + } + } + + } + + private playerChangeEvent(){ + + + if (this.currentPlayer.userId != this.thisPlayer.userId) { + this.pulled = false; + this.titleService.setTitle("UNO - " + this.thisPlayer.nickname); + } else { + this.titleService.setTitle("DU BIST DRAN! - UNO - " + this.thisPlayer.nickname); + + } + //get next player + let index = 0; + this.playerList.forEach((obj, ix) => { + if (obj.userId == this.currentPlayer.userId) {index = ix;} + }); + let newIndex = index + this.direction; + if (newIndex < 0) { newIndex += this.playerList.length } + if (newIndex > ( this.playerList.length - 1)) { newIndex -= this.playerList.length } + this.nextPlayerId = this.playerList[newIndex].userId; + + if (this.currentPlayer.userId != this.lastPlayer){ + if (this.currentPlayer.userId == this.thisPlayer.userId){ + let audio = new Audio(); + audio.src = "../assets/when.mp3"; + audio.load(); + audio.play(); + } + this.lastPlayer = this.currentPlayer.userId; + } + + } + + private processPlayerUpdate(){ + const userId = localStorage.getItem('uid'); + for (let pi = 0; pi < this.playerList.length; pi++) { + if (this.playerList[pi].userId == userId) { + this.thisPlayer = this.playerList[pi]; + } + } + for (let pi = 0; pi < this.winnerList.length; pi++) { + if (this.winnerList[pi].userId == userId) { + this.thisPlayer = this.winnerList[pi]; + } + } + } + + + //old + + private comparer(otherArray) { + return function (current) { + return otherArray.filter(function (other) { + return other.value == current.value && other.display == current.display + }).length == 0; + } + } + + private updateHand(newCards) { + if (!newCards) { return; } //wenn fertig oder nicht begonnen tue nichts + //prüfe differenz + + newCards.forEach(el => { + if ((this.handCards.filter(nel => el.id == nel.id)).length == 0) { + this.handCards.push(el); + } + }); + this.handCards.forEach(el => { + if ((newCards.filter(nel => el.id == nel.id)).length == 0) { + this.handCards = this.handCards.filter(fel => fel.id != el.id); + } + }); + + } + + + public leave() { + this.conn.next({action: 'uibutton',button:'leave'}); + this.conn.complete(); + } + + public ready() { + this.conn.next({action: 'uibutton',button:'ready'}); + } + + public skip() { + this.conn.next({action: 'uibutton',button:'skip'}); + } + + public uno() { + this.conn.next({action: 'uibutton',button:'uno'}); + } + + public pull() { + this.conn.next({action: 'uibutton',button:'pull'}); + } + + public play(card) { + // + let doubleFound = -1; + this.handCards.forEach(el => { + if (el.id != card.id && el.color == card.color && el.number == card.number && card.number != '+2' && card.number != 'wild+4'){ + doubleFound = el.id; + this.doubleCard = el; + } + }) + console.log(doubleFound) + if (doubleFound > -1){ + document.getElementById("open_rdouble_modal").click(); + }else{ + this.playSingle(); + } + } + + public playSingle() { + console.log("play single ", this.selectedCard) + this.conn.next({action: 'uibutton',button:'play',card:this.selectedCard}); + } + + public playDouble(){ + console.log("play double ", this.selectedCard) + this.conn.next({action: 'uibutton',button:'double',card:[this.selectedCard,this.doubleCard]}); + } + + public playwild(number, color) { + this.conn.next({action: 'uibutton',button:'play',card:{ + id: this.selectedCard.id, + number: number, + color: color, + tx: this.selectedCard.tx, + }}); + } + +} diff --git a/UNO-Angular/src/app/start/start.component.html b/UNO-Angular/src/app/start/start.component.html new file mode 100644 index 0000000..9b4cf74 --- /dev/null +++ b/UNO-Angular/src/app/start/start.component.html @@ -0,0 +1,22 @@ +
+
+
+
+

Wie heißt du ?

+
+
+
+ @ +
+ +
+ Der Benutzername ist zu lange + Der Benutzername ist zu kurz +
+ +
+
+ +
+
+
diff --git a/UNO-Angular/src/app/start/start.component.scss b/UNO-Angular/src/app/start/start.component.scss new file mode 100644 index 0000000..09b7581 --- /dev/null +++ b/UNO-Angular/src/app/start/start.component.scss @@ -0,0 +1,18 @@ +.start-box { + max-width: 500px; + float: none; + margin: 0 auto; + margin-top: auto; + margin-bottom: auto; +} + +body { + background-color: gray; +} + +.error { + color: red; + font-weight: 500; + padding-bottom: 2rem; + font-size: 1.5rem; +} \ No newline at end of file diff --git a/UNO-Angular/src/app/start/start.component.ts b/UNO-Angular/src/app/start/start.component.ts new file mode 100644 index 0000000..e95d4d7 --- /dev/null +++ b/UNO-Angular/src/app/start/start.component.ts @@ -0,0 +1,49 @@ +import { Component, OnInit } from '@angular/core'; +import { HttpClient } from "@angular/common/http"; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-start', + templateUrl: './start.component.html', + styleUrls: ['./start.component.scss'] +}) +export class StartComponent implements OnInit { + + nick: string = ""; + buttonEnabled: boolean = true; + + constructor( + private httpClient: HttpClient, + private router: Router + ) { } + + ngOnInit(): void { + let uname = localStorage.getItem('username'); + if (uname) { + this.nick = uname; + } + } + + public start() { + //this.buttonEnabled = false; + localStorage.setItem('username', this.nick); + this.router.navigate(['/browser']); + /* + setTimeout(() => { + console.log("start " + this.nick); + this.httpClient.post("/unogame/join", { nick: this.nick }).subscribe(result => { + const cres: any = result; + this.buttonEnabled = true; + if (cres.success) { + console.log(cres.data) + localStorage.setItem('uid', cres.data.userId); + localStorage.setItem('username', this.nick); + this.router.navigate(['/game']); + } + }); + }, 200); + */ + + + } +} diff --git a/UNO-Angular/src/assets/.gitkeep b/UNO-Angular/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/UNO-Angular/src/assets/cardPlace1.wav b/UNO-Angular/src/assets/cardPlace1.wav new file mode 100644 index 0000000..c22e195 Binary files /dev/null and b/UNO-Angular/src/assets/cardPlace1.wav differ diff --git a/UNO-Angular/src/assets/cardSlide7.wav b/UNO-Angular/src/assets/cardSlide7.wav new file mode 100644 index 0000000..b7aa57f Binary files /dev/null and b/UNO-Angular/src/assets/cardSlide7.wav differ diff --git a/UNO-Angular/src/assets/cards/000.png b/UNO-Angular/src/assets/cards/000.png new file mode 100644 index 0000000..70fbd2b Binary files /dev/null and b/UNO-Angular/src/assets/cards/000.png differ diff --git a/UNO-Angular/src/assets/cards/110.png b/UNO-Angular/src/assets/cards/110.png new file mode 100644 index 0000000..47644f9 Binary files /dev/null and b/UNO-Angular/src/assets/cards/110.png differ diff --git a/UNO-Angular/src/assets/cards/111.png b/UNO-Angular/src/assets/cards/111.png new file mode 100644 index 0000000..51dd4c2 Binary files /dev/null and b/UNO-Angular/src/assets/cards/111.png differ diff --git a/UNO-Angular/src/assets/cards/112.png b/UNO-Angular/src/assets/cards/112.png new file mode 100644 index 0000000..239b8e1 Binary files /dev/null and b/UNO-Angular/src/assets/cards/112.png differ diff --git a/UNO-Angular/src/assets/cards/113.png b/UNO-Angular/src/assets/cards/113.png new file mode 100644 index 0000000..56e2b0d Binary files /dev/null and b/UNO-Angular/src/assets/cards/113.png differ diff --git a/UNO-Angular/src/assets/cards/114.png b/UNO-Angular/src/assets/cards/114.png new file mode 100644 index 0000000..8e42956 Binary files /dev/null and b/UNO-Angular/src/assets/cards/114.png differ diff --git a/UNO-Angular/src/assets/cards/115.png b/UNO-Angular/src/assets/cards/115.png new file mode 100644 index 0000000..3bab16e Binary files /dev/null and b/UNO-Angular/src/assets/cards/115.png differ diff --git a/UNO-Angular/src/assets/cards/116.png b/UNO-Angular/src/assets/cards/116.png new file mode 100644 index 0000000..395df3b Binary files /dev/null and b/UNO-Angular/src/assets/cards/116.png differ diff --git a/UNO-Angular/src/assets/cards/117.png b/UNO-Angular/src/assets/cards/117.png new file mode 100644 index 0000000..90ad0f1 Binary files /dev/null and b/UNO-Angular/src/assets/cards/117.png differ diff --git a/UNO-Angular/src/assets/cards/118.png b/UNO-Angular/src/assets/cards/118.png new file mode 100644 index 0000000..ef9cb28 Binary files /dev/null and b/UNO-Angular/src/assets/cards/118.png differ diff --git a/UNO-Angular/src/assets/cards/119.png b/UNO-Angular/src/assets/cards/119.png new file mode 100644 index 0000000..2fee178 Binary files /dev/null and b/UNO-Angular/src/assets/cards/119.png differ diff --git a/UNO-Angular/src/assets/cards/120.png b/UNO-Angular/src/assets/cards/120.png new file mode 100644 index 0000000..762b830 Binary files /dev/null and b/UNO-Angular/src/assets/cards/120.png differ diff --git a/UNO-Angular/src/assets/cards/121.png b/UNO-Angular/src/assets/cards/121.png new file mode 100644 index 0000000..57d6c45 Binary files /dev/null and b/UNO-Angular/src/assets/cards/121.png differ diff --git a/UNO-Angular/src/assets/cards/122.png b/UNO-Angular/src/assets/cards/122.png new file mode 100644 index 0000000..6021e4e Binary files /dev/null and b/UNO-Angular/src/assets/cards/122.png differ diff --git a/UNO-Angular/src/assets/cards/123.png b/UNO-Angular/src/assets/cards/123.png new file mode 100644 index 0000000..8ebd6e5 Binary files /dev/null and b/UNO-Angular/src/assets/cards/123.png differ diff --git a/UNO-Angular/src/assets/cards/124.png b/UNO-Angular/src/assets/cards/124.png new file mode 100644 index 0000000..54432f2 Binary files /dev/null and b/UNO-Angular/src/assets/cards/124.png differ diff --git a/UNO-Angular/src/assets/cards/125.png b/UNO-Angular/src/assets/cards/125.png new file mode 100644 index 0000000..2e11539 Binary files /dev/null and b/UNO-Angular/src/assets/cards/125.png differ diff --git a/UNO-Angular/src/assets/cards/126.png b/UNO-Angular/src/assets/cards/126.png new file mode 100644 index 0000000..32cf551 Binary files /dev/null and b/UNO-Angular/src/assets/cards/126.png differ diff --git a/UNO-Angular/src/assets/cards/127.png b/UNO-Angular/src/assets/cards/127.png new file mode 100644 index 0000000..7ecd2a5 Binary files /dev/null and b/UNO-Angular/src/assets/cards/127.png differ diff --git a/UNO-Angular/src/assets/cards/128.png b/UNO-Angular/src/assets/cards/128.png new file mode 100644 index 0000000..3113e6a Binary files /dev/null and b/UNO-Angular/src/assets/cards/128.png differ diff --git a/UNO-Angular/src/assets/cards/129.png b/UNO-Angular/src/assets/cards/129.png new file mode 100644 index 0000000..4106850 Binary files /dev/null and b/UNO-Angular/src/assets/cards/129.png differ diff --git a/UNO-Angular/src/assets/cards/130.png b/UNO-Angular/src/assets/cards/130.png new file mode 100644 index 0000000..1cf818a Binary files /dev/null and b/UNO-Angular/src/assets/cards/130.png differ diff --git a/UNO-Angular/src/assets/cards/131.png b/UNO-Angular/src/assets/cards/131.png new file mode 100644 index 0000000..0715cd8 Binary files /dev/null and b/UNO-Angular/src/assets/cards/131.png differ diff --git a/UNO-Angular/src/assets/cards/132.png b/UNO-Angular/src/assets/cards/132.png new file mode 100644 index 0000000..b89bdc7 Binary files /dev/null and b/UNO-Angular/src/assets/cards/132.png differ diff --git a/UNO-Angular/src/assets/cards/133.png b/UNO-Angular/src/assets/cards/133.png new file mode 100644 index 0000000..d57130a Binary files /dev/null and b/UNO-Angular/src/assets/cards/133.png differ diff --git a/UNO-Angular/src/assets/cards/134.png b/UNO-Angular/src/assets/cards/134.png new file mode 100644 index 0000000..3bd0a48 Binary files /dev/null and b/UNO-Angular/src/assets/cards/134.png differ diff --git a/UNO-Angular/src/assets/cards/135.png b/UNO-Angular/src/assets/cards/135.png new file mode 100644 index 0000000..7336cfb Binary files /dev/null and b/UNO-Angular/src/assets/cards/135.png differ diff --git a/UNO-Angular/src/assets/cards/136.png b/UNO-Angular/src/assets/cards/136.png new file mode 100644 index 0000000..f8e6517 Binary files /dev/null and b/UNO-Angular/src/assets/cards/136.png differ diff --git a/UNO-Angular/src/assets/cards/137.png b/UNO-Angular/src/assets/cards/137.png new file mode 100644 index 0000000..a2b1d0b Binary files /dev/null and b/UNO-Angular/src/assets/cards/137.png differ diff --git a/UNO-Angular/src/assets/cards/138.png b/UNO-Angular/src/assets/cards/138.png new file mode 100644 index 0000000..2ad0f3c Binary files /dev/null and b/UNO-Angular/src/assets/cards/138.png differ diff --git a/UNO-Angular/src/assets/cards/139.png b/UNO-Angular/src/assets/cards/139.png new file mode 100644 index 0000000..6fc645f Binary files /dev/null and b/UNO-Angular/src/assets/cards/139.png differ diff --git a/UNO-Angular/src/assets/cards/140.png b/UNO-Angular/src/assets/cards/140.png new file mode 100644 index 0000000..09933dd Binary files /dev/null and b/UNO-Angular/src/assets/cards/140.png differ diff --git a/UNO-Angular/src/assets/cards/141.png b/UNO-Angular/src/assets/cards/141.png new file mode 100644 index 0000000..6d6aadb Binary files /dev/null and b/UNO-Angular/src/assets/cards/141.png differ diff --git a/UNO-Angular/src/assets/cards/142.png b/UNO-Angular/src/assets/cards/142.png new file mode 100644 index 0000000..c967f90 Binary files /dev/null and b/UNO-Angular/src/assets/cards/142.png differ diff --git a/UNO-Angular/src/assets/cards/143.png b/UNO-Angular/src/assets/cards/143.png new file mode 100644 index 0000000..c819297 Binary files /dev/null and b/UNO-Angular/src/assets/cards/143.png differ diff --git a/UNO-Angular/src/assets/cards/144.png b/UNO-Angular/src/assets/cards/144.png new file mode 100644 index 0000000..514f3fe Binary files /dev/null and b/UNO-Angular/src/assets/cards/144.png differ diff --git a/UNO-Angular/src/assets/cards/145.png b/UNO-Angular/src/assets/cards/145.png new file mode 100644 index 0000000..ecddd55 Binary files /dev/null and b/UNO-Angular/src/assets/cards/145.png differ diff --git a/UNO-Angular/src/assets/cards/146.png b/UNO-Angular/src/assets/cards/146.png new file mode 100644 index 0000000..1e30da6 Binary files /dev/null and b/UNO-Angular/src/assets/cards/146.png differ diff --git a/UNO-Angular/src/assets/cards/147.png b/UNO-Angular/src/assets/cards/147.png new file mode 100644 index 0000000..b70c272 Binary files /dev/null and b/UNO-Angular/src/assets/cards/147.png differ diff --git a/UNO-Angular/src/assets/cards/148.png b/UNO-Angular/src/assets/cards/148.png new file mode 100644 index 0000000..075fd8a Binary files /dev/null and b/UNO-Angular/src/assets/cards/148.png differ diff --git a/UNO-Angular/src/assets/cards/149.png b/UNO-Angular/src/assets/cards/149.png new file mode 100644 index 0000000..e52ef45 Binary files /dev/null and b/UNO-Angular/src/assets/cards/149.png differ diff --git a/UNO-Angular/src/assets/cards/211.png b/UNO-Angular/src/assets/cards/211.png new file mode 100644 index 0000000..4f88796 Binary files /dev/null and b/UNO-Angular/src/assets/cards/211.png differ diff --git a/UNO-Angular/src/assets/cards/212.png b/UNO-Angular/src/assets/cards/212.png new file mode 100644 index 0000000..9270c3d Binary files /dev/null and b/UNO-Angular/src/assets/cards/212.png differ diff --git a/UNO-Angular/src/assets/cards/213.png b/UNO-Angular/src/assets/cards/213.png new file mode 100644 index 0000000..5c946a2 Binary files /dev/null and b/UNO-Angular/src/assets/cards/213.png differ diff --git a/UNO-Angular/src/assets/cards/221.png b/UNO-Angular/src/assets/cards/221.png new file mode 100644 index 0000000..0d12512 Binary files /dev/null and b/UNO-Angular/src/assets/cards/221.png differ diff --git a/UNO-Angular/src/assets/cards/222.png b/UNO-Angular/src/assets/cards/222.png new file mode 100644 index 0000000..4be633b Binary files /dev/null and b/UNO-Angular/src/assets/cards/222.png differ diff --git a/UNO-Angular/src/assets/cards/223.png b/UNO-Angular/src/assets/cards/223.png new file mode 100644 index 0000000..86f65f8 Binary files /dev/null and b/UNO-Angular/src/assets/cards/223.png differ diff --git a/UNO-Angular/src/assets/cards/231.png b/UNO-Angular/src/assets/cards/231.png new file mode 100644 index 0000000..b8720e2 Binary files /dev/null and b/UNO-Angular/src/assets/cards/231.png differ diff --git a/UNO-Angular/src/assets/cards/232.png b/UNO-Angular/src/assets/cards/232.png new file mode 100644 index 0000000..c3c84ee Binary files /dev/null and b/UNO-Angular/src/assets/cards/232.png differ diff --git a/UNO-Angular/src/assets/cards/233.png b/UNO-Angular/src/assets/cards/233.png new file mode 100644 index 0000000..3e6c834 Binary files /dev/null and b/UNO-Angular/src/assets/cards/233.png differ diff --git a/UNO-Angular/src/assets/cards/241.png b/UNO-Angular/src/assets/cards/241.png new file mode 100644 index 0000000..6343f2b Binary files /dev/null and b/UNO-Angular/src/assets/cards/241.png differ diff --git a/UNO-Angular/src/assets/cards/242.png b/UNO-Angular/src/assets/cards/242.png new file mode 100644 index 0000000..532b8b3 Binary files /dev/null and b/UNO-Angular/src/assets/cards/242.png differ diff --git a/UNO-Angular/src/assets/cards/243.png b/UNO-Angular/src/assets/cards/243.png new file mode 100644 index 0000000..16dffa6 Binary files /dev/null and b/UNO-Angular/src/assets/cards/243.png differ diff --git a/UNO-Angular/src/assets/cards/301.png b/UNO-Angular/src/assets/cards/301.png new file mode 100644 index 0000000..28f97df Binary files /dev/null and b/UNO-Angular/src/assets/cards/301.png differ diff --git a/UNO-Angular/src/assets/cards/302.png b/UNO-Angular/src/assets/cards/302.png new file mode 100644 index 0000000..9d857e7 Binary files /dev/null and b/UNO-Angular/src/assets/cards/302.png differ diff --git a/UNO-Angular/src/assets/cards/311.png b/UNO-Angular/src/assets/cards/311.png new file mode 100644 index 0000000..62a44b0 Binary files /dev/null and b/UNO-Angular/src/assets/cards/311.png differ diff --git a/UNO-Angular/src/assets/cards/312.png b/UNO-Angular/src/assets/cards/312.png new file mode 100644 index 0000000..05ab11b Binary files /dev/null and b/UNO-Angular/src/assets/cards/312.png differ diff --git a/UNO-Angular/src/assets/cards/321.png b/UNO-Angular/src/assets/cards/321.png new file mode 100644 index 0000000..ddde7e8 Binary files /dev/null and b/UNO-Angular/src/assets/cards/321.png differ diff --git a/UNO-Angular/src/assets/cards/322.png b/UNO-Angular/src/assets/cards/322.png new file mode 100644 index 0000000..c69f4fe Binary files /dev/null and b/UNO-Angular/src/assets/cards/322.png differ diff --git a/UNO-Angular/src/assets/cards/331.png b/UNO-Angular/src/assets/cards/331.png new file mode 100644 index 0000000..c688391 Binary files /dev/null and b/UNO-Angular/src/assets/cards/331.png differ diff --git a/UNO-Angular/src/assets/cards/332.png b/UNO-Angular/src/assets/cards/332.png new file mode 100644 index 0000000..1a1a84c Binary files /dev/null and b/UNO-Angular/src/assets/cards/332.png differ diff --git a/UNO-Angular/src/assets/cards/341.png b/UNO-Angular/src/assets/cards/341.png new file mode 100644 index 0000000..5a5180e Binary files /dev/null and b/UNO-Angular/src/assets/cards/341.png differ diff --git a/UNO-Angular/src/assets/cards/342.png b/UNO-Angular/src/assets/cards/342.png new file mode 100644 index 0000000..516b1e5 Binary files /dev/null and b/UNO-Angular/src/assets/cards/342.png differ diff --git a/UNO-Angular/src/assets/cards/felt.jpg b/UNO-Angular/src/assets/cards/felt.jpg new file mode 100644 index 0000000..9f95a81 Binary files /dev/null and b/UNO-Angular/src/assets/cards/felt.jpg differ diff --git a/UNO-Angular/src/assets/felt.jpg b/UNO-Angular/src/assets/felt.jpg new file mode 100644 index 0000000..9f95a81 Binary files /dev/null and b/UNO-Angular/src/assets/felt.jpg differ diff --git a/UNO-Angular/src/assets/skip.wav b/UNO-Angular/src/assets/skip.wav new file mode 100644 index 0000000..8718ca9 Binary files /dev/null and b/UNO-Angular/src/assets/skip.wav differ diff --git a/UNO-Angular/src/assets/tick.wav b/UNO-Angular/src/assets/tick.wav new file mode 100644 index 0000000..26c00e1 Binary files /dev/null and b/UNO-Angular/src/assets/tick.wav differ diff --git a/UNO-Angular/src/assets/when.mp3 b/UNO-Angular/src/assets/when.mp3 new file mode 100644 index 0000000..9dc8044 Binary files /dev/null and b/UNO-Angular/src/assets/when.mp3 differ diff --git a/UNO-Angular/src/environments/environment.prod.ts b/UNO-Angular/src/environments/environment.prod.ts new file mode 100644 index 0000000..3612073 --- /dev/null +++ b/UNO-Angular/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/UNO-Angular/src/environments/environment.ts b/UNO-Angular/src/environments/environment.ts new file mode 100644 index 0000000..7b4f817 --- /dev/null +++ b/UNO-Angular/src/environments/environment.ts @@ -0,0 +1,16 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/UNO-Angular/src/favicon.ico b/UNO-Angular/src/favicon.ico new file mode 100644 index 0000000..997406a Binary files /dev/null and b/UNO-Angular/src/favicon.ico differ diff --git a/UNO-Angular/src/index.html b/UNO-Angular/src/index.html new file mode 100644 index 0000000..6ce211d --- /dev/null +++ b/UNO-Angular/src/index.html @@ -0,0 +1,16 @@ + + + + + Uno + + + + + + + + + + + diff --git a/UNO-Angular/src/main.ts b/UNO-Angular/src/main.ts new file mode 100644 index 0000000..c7b673c --- /dev/null +++ b/UNO-Angular/src/main.ts @@ -0,0 +1,12 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/UNO-Angular/src/polyfills.ts b/UNO-Angular/src/polyfills.ts new file mode 100644 index 0000000..ba33a91 --- /dev/null +++ b/UNO-Angular/src/polyfills.ts @@ -0,0 +1,69 @@ +/*************************************************************************************************** + * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates. + */ +import '@angular/localize/init'; +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +(window as any).global = window; \ No newline at end of file diff --git a/UNO-Angular/src/styles.scss b/UNO-Angular/src/styles.scss new file mode 100644 index 0000000..7e7239a --- /dev/null +++ b/UNO-Angular/src/styles.scss @@ -0,0 +1,4 @@ +/* You can add global styles to this file, and also import other style files */ + +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } diff --git a/UNO-Angular/src/test.ts b/UNO-Angular/src/test.ts new file mode 100644 index 0000000..50193eb --- /dev/null +++ b/UNO-Angular/src/test.ts @@ -0,0 +1,25 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/zone-testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: { + context(path: string, deep?: boolean, filter?: RegExp): { + keys(): string[]; + (id: string): T; + }; +}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/UNO-Angular/tsconfig.app.json b/UNO-Angular/tsconfig.app.json new file mode 100644 index 0000000..f758d98 --- /dev/null +++ b/UNO-Angular/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": [ + "src/main.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/UNO-Angular/tsconfig.json b/UNO-Angular/tsconfig.json new file mode 100644 index 0000000..8c4ef3b --- /dev/null +++ b/UNO-Angular/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "module": "esnext", + "moduleResolution": "node", + "importHelpers": true, + "target": "es2015", + "lib": [ + "es2018", + "dom" + ] + }, + "angularCompilerOptions": { + "fullTemplateTypeCheck": true, + "strictInjectionParameters": true + } +} diff --git a/UNO-Angular/tsconfig.spec.json b/UNO-Angular/tsconfig.spec.json new file mode 100644 index 0000000..6400fde --- /dev/null +++ b/UNO-Angular/tsconfig.spec.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "src/test.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/UNO-Angular/tslint.json b/UNO-Angular/tslint.json new file mode 100644 index 0000000..d92ff5d --- /dev/null +++ b/UNO-Angular/tslint.json @@ -0,0 +1,148 @@ +{ + "extends": "tslint:recommended", + "rules": { + "align": { + "options": [ + "parameters", + "statements" + ] + }, + "array-type": false, + "arrow-return-shorthand": true, + "curly": true, + "deprecation": { + "severity": "warning" + }, + "component-class-suffix": true, + "contextual-lifecycle": true, + "directive-class-suffix": true, + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ], + "eofline": true, + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "import-spacing": true, + "indent": { + "options": [ + "spaces" + ] + }, + "max-classes-per-file": false, + "max-line-length": [ + true, + 140 + ], + "member-ordering": [ + true, + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } + ], + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-empty": false, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-non-null-assertion": true, + "no-redundant-jsdoc": true, + "no-switch-case-fall-through": true, + "no-var-requires": false, + "object-literal-key-quotes": [ + true, + "as-needed" + ], + "quotemark": [ + true, + "single" + ], + "semicolon": { + "options": [ + "always" + ] + }, + "space-before-function-paren": { + "options": { + "anonymous": "never", + "asyncArrow": "always", + "constructor": "never", + "method": "never", + "named": "never" + } + }, + "typedef-whitespace": { + "options": [ + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } + ] + }, + "variable-name": { + "options": [ + "ban-keywords", + "check-format", + "allow-pascal-case" + ] + }, + "whitespace": { + "options": [ + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type", + "check-typecast" + ] + }, + "no-conflicting-lifecycle": true, + "no-host-metadata-property": true, + "no-input-rename": true, + "no-inputs-metadata-property": true, + "no-output-native": true, + "no-output-on-prefix": true, + "no-output-rename": true, + "no-outputs-metadata-property": true, + "template-banana-in-box": true, + "template-no-negated-async": true, + "use-lifecycle-interface": true, + "use-pipe-transform-interface": true + }, + "rulesDirectory": [ + "codelyzer" + ] +} \ No newline at end of file diff --git a/UNO-Backend/README.md b/UNO-Backend/README.md new file mode 100644 index 0000000..1db158d --- /dev/null +++ b/UNO-Backend/README.md @@ -0,0 +1,3 @@ +# UNO-Backend + + diff --git a/UNO-Backend/UNO-Backend.njsproj b/UNO-Backend/UNO-Backend.njsproj new file mode 100644 index 0000000..2e49f70 --- /dev/null +++ b/UNO-Backend/UNO-Backend.njsproj @@ -0,0 +1,117 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + UNO-Backend + UNO-Backend + + + + Debug + 2.0 + d5b44f82-6712-42ae-bcb7-ff5775f7b3dc + . + server.ts + True + + + . + . + v4.0 + {3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD} + 1337 + true + true + + + true + + + true + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + + + + + + + + + + + + + Code + + + Code + + + + + + + + False + True + 0 + / + http://localhost:48022/ + False + True + http://localhost:1337 + False + + + + + + + CurrentPage + True + False + False + False + + + + + + + + + False + False + + + + + \ No newline at end of file diff --git a/UNO-Backend/UNO-Backend.njsproj.user b/UNO-Backend/UNO-Backend.njsproj.user new file mode 100644 index 0000000..c8ee29a --- /dev/null +++ b/UNO-Backend/UNO-Backend.njsproj.user @@ -0,0 +1,6 @@ + + + + Debug|Any CPU + + \ No newline at end of file diff --git a/UNO-Backend/bin/Microsoft.NodejsTools.WebRole.dll b/UNO-Backend/bin/Microsoft.NodejsTools.WebRole.dll new file mode 100644 index 0000000..fd4cc4a Binary files /dev/null and b/UNO-Backend/bin/Microsoft.NodejsTools.WebRole.dll differ diff --git a/UNO-Backend/config/cards.json b/UNO-Backend/config/cards.json new file mode 100644 index 0000000..e1b34f1 --- /dev/null +++ b/UNO-Backend/config/cards.json @@ -0,0 +1,328 @@ +{ + "cards": [ + { + "color": "blue", + "number": "0", + "count": 1, + "tx": "/assets/cards/110.png" + }, + { + "color": "blue", + "number": "1", + "count": 2, + "tx": "/assets/cards/111.png" + }, + { + "color": "blue", + "number": "2", + "count": 2, + "tx": "/assets/cards/112.png" + }, + { + "color": "blue", + "number": "3", + "count": 2, + "tx": "/assets/cards/113.png" + }, + { + "color": "blue", + "number": "4", + "count": 2, + "tx": "/assets/cards/114.png" + }, + { + "color": "blue", + "number": "5", + "count": 2, + "tx": "/assets/cards/115.png" + }, + { + "color": "blue", + "number": "6", + "count": 2, + "tx": "/assets/cards/116.png" + }, + { + "color": "blue", + "number": "7", + "count": 2, + "tx": "/assets/cards/117.png" + }, + { + "color": "blue", + "number": "8", + "count": 2, + "tx": "/assets/cards/118.png" + }, + { + "color": "blue", + "number": "9", + "count": 2, + "tx": "/assets/cards/119.png" + }, + { + "color": "blue", + "number": "+2", + "count": 2, + "tx": "/assets/cards/211.png" + }, + { + "color": "blue", + "number": "skip", + "count": 2, + "tx": "/assets/cards/213.png" + }, + { + "color": "blue", + "number": "reverse", + "count": 2, + "tx": "/assets/cards/212.png" + }, + { + "color": "green", + "number": "0", + "count": 1, + "tx": "/assets/cards/120.png" + }, + { + "color": "green", + "number": "1", + "count": 2, + "tx": "/assets/cards/121.png" + }, + { + "color": "green", + "number": "2", + "count": 2, + "tx": "/assets/cards/122.png" + }, + { + "color": "green", + "number": "3", + "count": 2, + "tx": "/assets/cards/123.png" + }, + { + "color": "green", + "number": "4", + "count": 2, + "tx": "/assets/cards/124.png" + }, + { + "color": "green", + "number": "5", + "count": 2, + "tx": "/assets/cards/125.png" + }, + { + "color": "green", + "number": "6", + "count": 2, + "tx": "/assets/cards/126.png" + }, + { + "color": "green", + "number": "7", + "count": 2, + "tx": "/assets/cards/127.png" + }, + { + "color": "green", + "number": "8", + "count": 2, + "tx": "/assets/cards/128.png" + }, + { + "color": "green", + "number": "9", + "count": 2, + "tx": "/assets/cards/129.png" + }, + { + "color": "green", + "number": "+2", + "count": 2, + "tx": "/assets/cards/221.png" + }, + { + "color": "green", + "number": "skip", + "count": 2, + "tx": "/assets/cards/223.png" + }, + { + "color": "green", + "number": "reverse", + "count": 2, + "tx": "/assets/cards/222.png" + }, + { + "color": "yellow", + "number": "0", + "count": 1, + "tx": "/assets/cards/140.png" + }, + { + "color": "yellow", + "number": "1", + "count": 2, + "tx": "/assets/cards/141.png" + }, + { + "color": "yellow", + "number": "2", + "count": 2, + "tx": "/assets/cards/142.png" + }, + { + "color": "yellow", + "number": "3", + "count": 2, + "tx": "/assets/cards/143.png" + }, + { + "color": "yellow", + "number": "4", + "count": 2, + "tx": "/assets/cards/144.png" + }, + { + "color": "yellow", + "number": "5", + "count": 2, + "tx": "/assets/cards/145.png" + }, + { + "color": "yellow", + "number": "6", + "count": 2, + "tx": "/assets/cards/146.png" + }, + { + "color": "yellow", + "number": "7", + "count": 2, + "tx": "/assets/cards/147.png" + }, + { + "color": "yellow", + "number": "8", + "count": 2, + "tx": "/assets/cards/148.png" + }, + { + "color": "yellow", + "number": "9", + "count": 2, + "tx": "/assets/cards/149.png" + }, + { + "color": "yellow", + "number": "+2", + "count": 2, + "tx": "/assets/cards/241.png" + }, + { + "color": "yellow", + "number": "skip", + "count": 2, + "tx": "/assets/cards/243.png" + }, + { + "color": "yellow", + "number": "reverse", + "count": 2, + "tx": "/assets/cards/242.png" + }, + { + "color": "red", + "number": "0", + "count": 1, + "tx": "/assets/cards/130.png" + }, + { + "color": "red", + "number": "1", + "count": 2, + "tx": "/assets/cards/131.png" + }, + { + "color": "red", + "number": "2", + "count": 2, + "tx": "/assets/cards/132.png" + }, + { + "color": "red", + "number": "3", + "count": 2, + "tx": "/assets/cards/133.png" + }, + { + "color": "red", + "number": "4", + "count": 2, + "tx": "/assets/cards/134.png" + }, + { + "color": "red", + "number": "5", + "count": 2, + "tx": "/assets/cards/135.png" + }, + { + "color": "red", + "number": "6", + "count": 2, + "tx": "/assets/cards/136.png" + }, + { + "color": "red", + "number": "7", + "count": 2, + "tx": "/assets/cards/137.png" + }, + { + "color": "red", + "number": "8", + "count": 2, + "tx": "/assets/cards/138.png" + }, + { + "color": "red", + "number": "9", + "count": 2, + "tx": "/assets/cards/139.png" + }, + { + "color": "red", + "number": "+2", + "count": 2, + "tx": "/assets/cards/231.png" + }, + { + "color": "red", + "number": "skip", + "count": 2, + "tx": "/assets/cards/233.png" + }, + { + "color": "red", + "number": "reverse", + "count": 2, + "tx": "/assets/cards/232.png" + }, + { + "color": "select", + "number": "wild", + "count": 4, + "tx": "/assets/cards/301.png" + }, + { + "color": "select", + "number": "wild+4", + "count": 4, + "tx": "/assets/cards/302.png" + } + ] +} \ No newline at end of file diff --git a/UNO-Backend/config/game.json b/UNO-Backend/config/game.json new file mode 100644 index 0000000..2b3534b --- /dev/null +++ b/UNO-Backend/config/game.json @@ -0,0 +1,9 @@ +{ + "kickIdlePlayers": true, + "idleTimeout": 100000, + "serverName": "Dennis Ultrakrasser Uno Server", + "maxPlayers": 10, + "password": "SagIchDirNicht!", + "startCards": 7, + "testMode": false +} \ No newline at end of file diff --git a/UNO-Backend/dockerfile b/UNO-Backend/dockerfile new file mode 100644 index 0000000..6fc40e7 --- /dev/null +++ b/UNO-Backend/dockerfile @@ -0,0 +1,22 @@ +FROM node:12.21-alpine +RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app + +COPY ./dist/out-tsc /home/node/backend +COPY ./package.json /home/node/backend/package.json +WORKDIR /home/node/backend +RUN ls -ltra +#RUN rm node_modules -R +#RUN rm package-lock.json + +#RUN chown node /opt/citron3 -Rf + +RUN chmod -R 777 /home/node/backend + +USER node + +RUN npm install + +EXPOSE 3000 + +CMD [ "node", "--experimental-json-modules", "server.js"] + diff --git a/UNO-Backend/obj/Debug/UNO-Backend.njsproj.FileListAbsolute.txt b/UNO-Backend/obj/Debug/UNO-Backend.njsproj.FileListAbsolute.txt new file mode 100644 index 0000000..a383e2d --- /dev/null +++ b/UNO-Backend/obj/Debug/UNO-Backend.njsproj.FileListAbsolute.txt @@ -0,0 +1,2 @@ +C:\Users\dennisgunia\source\repos\UNO-Backend\UNO-Backend\obj\Debug\UNO-Backend.njsprojAssemblyReference.cache +C:\Users\dennisgunia\source\repos\UNO-Backend\UNO-Backend\bin\Microsoft.NodejsTools.WebRole.dll diff --git a/UNO-Backend/obj/Debug/UNO-Backend.njsprojAssemblyReference.cache b/UNO-Backend/obj/Debug/UNO-Backend.njsprojAssemblyReference.cache new file mode 100644 index 0000000..ab32277 Binary files /dev/null and b/UNO-Backend/obj/Debug/UNO-Backend.njsprojAssemblyReference.cache differ diff --git a/UNO-Backend/package.json b/UNO-Backend/package.json new file mode 100644 index 0000000..7510b8d --- /dev/null +++ b/UNO-Backend/package.json @@ -0,0 +1,30 @@ +{ + "name": "uno-backend", + "version": "0.0.0", + "description": "UNO-Backend", + "main": "server.js", + "author": { + "name": "" + }, + "type": "module", + "scripts": { + "build": "tsc --build ", + "clean": "tsc --build --clean" + }, + "devDependencies": { + "@types/express": "^4.17.3", + "@types/node": "^8.10.59", + "@types/ws": "^7.2.3", + "typescript": "^3.9.9" + }, + "dependencies": { + "body-parser": "^1.19.0", + "express": "^4.17.1", + "express-ws": "^4.0.0", + "nvm": "0.0.4", + "ts-node": "^9.1.1", + "tsc": "^1.20150623.0", + "tslib": "^2.1.0", + "ws": "^7.2.3" + } +} diff --git a/UNO-Backend/server.ts b/UNO-Backend/server.ts new file mode 100644 index 0000000..836f81f --- /dev/null +++ b/UNO-Backend/server.ts @@ -0,0 +1,69 @@ +import { UnoSockets } from './src/gameLogic/UnoSocket.js' +import express from "express"; +import bodyParser from 'body-parser' +import { UnoSession} from './src/gameLogic/UnoSession.js' +import expressWs from 'express-ws'; +import http from 'http'; +import WebSocket from 'ws'; +import { UnoLobby } from './src/lobby/UnoLobby.js'; +import { resp } from './src/interface/rest/response.js'; +import { watch } from 'fs'; + + +const app = express(); +app.use(bodyParser.json()) + + +let unoLobby = new UnoLobby(); + + +app.get('/', (req, res) => res.send('Hello World!')); + + +//lobby requests +app.get('/unogame/lobbies', (req,res)=>{ + resp(req, res, true, unoLobby.getLobbies(), 200); +}) + +//lobby requests +app.post('/unogame/create', (req,res)=>{ + resp(req, res, true, { + id: unoLobby.createLobby(req.body), + }, 200); +}) + +//lobby join +app.post('/unogame/join', (req,res)=>{ + let data = unoLobby.joinLobby(req.body.nick,req.body.lid); + resp(req, res, data[0], data[1], 200); +}) + + + +const port = 3000; +const server = app.listen(port, () => console.log(`Example app listening on port ${port}!`)); + + + + +const wss = new WebSocket.Server({ server }); + +wss.on('connection', (ws: WebSocket) => { + console.log("connecting socket...") + ws.on('message', (message: string) => { actionHandler(message,ws);}); + ws.send('{"msg":"Hello!"}'); +}); + + +function actionHandler(message: string, ws: WebSocket){ + try{ + const command = JSON.parse(message); + console.log(command); + if (command.action == "register"){ + unoLobby.registerLobby(message,ws) + } + }catch(ex){ + ws.send('{"reply":"Error"}'); + } +} + diff --git a/UNO-Backend/src/gameLogic/UiEvents.ts b/UNO-Backend/src/gameLogic/UiEvents.ts new file mode 100644 index 0000000..2919433 --- /dev/null +++ b/UNO-Backend/src/gameLogic/UiEvents.ts @@ -0,0 +1,126 @@ +import { UnoSession, UnoCard, UnoPlayer, } from './UnoSession.js'; +import * as logic from './UnoRules.js' +import * as Log from './../util/logging.js' + +export function processUiEvents(game: UnoSession, player: UnoPlayer, command: any){ + switch(command.button){ + case 'ready': + p_ready(game,player); + break; + case 'leave': + p_leave(game,player); + break; + case 'play': + p_play(game,player,command); + break + case 'double': + p_double(game,player,command); + break; + case 'leave': + p_leave(game,player); + break; + case 'pull': + p_pull(game,player); + break; + case 'skip': + p_skip(game,player); + break; + case 'uno': + p_uno(game,player); + break; + } +} + +function p_ready (game: UnoSession, player: UnoPlayer){ + if (game.started) { + player.socket.send(JSON.stringify({"notify":"game already started"})) + } else { + player.ready = true; + Log.logNotice("Player " + player.nickname + " (" + player.userId + ") is ready"); + game.unoChat.writeMessage(player.nickname + " ist bereit"); + game.checkReady(); + game.clientUpdatePlayer(player); + } +} + +function p_leave (game: UnoSession, player: UnoPlayer){ + if (game.started) { + logic.leave(game,player) + } else { + //remove player + const newPlayers = game.players.filter(el => el.userId != player.userId); + Log.logNotice("Player " + player.nickname + " (" + player.userId + ") left in lobby"); + game.unoChat.writeMessage(player.nickname + " hat die Lobby verlassen"); + game.players = newPlayers; + game.checkReady(); + player.socket.send(JSON.stringify({kick:"Spiel verlassen."})); + game.clientRemovePlayer(player); + } +} + +function p_play(game: UnoSession, player: UnoPlayer,command:any) { + if (!game.started) { + player.socket.send(JSON.stringify({"notify":"game not started"})) + } else { + let ret = logic.playCard(game, player, command.card) + player.socket.send(JSON.stringify({ + notify: ret[1], + warning: ret[0], + })) + game.clientUpdatePlayer(player); + } +} + +function p_double(game: UnoSession, player: UnoPlayer,command:any) { + if (!game.started) { + player.socket.send(JSON.stringify({"notify":"game not started"})) + } else { + let ret = logic.playCard(game, player, command.card[0], command.card[1]) + player.socket.send(JSON.stringify({ + notify: ret[1], + warning: ret[0], + })) + game.clientUpdatePlayer(player); + } +} + +function p_pull(game: UnoSession, player: UnoPlayer) { + if (!game.started) { + player.socket.send(JSON.stringify({"notify":"game not started"})) + } else { + let ret = logic.pull(game, player) + player.socket.send(JSON.stringify({ + notify: ret[1], + warning: ret[0], + })) + game.clientUpdatePlayer(player); + } + + +} + +function p_skip(game: UnoSession, player: UnoPlayer) { + if (!game.started) { + player.socket.send(JSON.stringify({"notify":"game not started"})) + } else { + let ret = logic.skip(game, player) + player.socket.send(JSON.stringify({ + notify: ret[1], + warning: ret[0], + })) + + } +} + +function p_uno(game: UnoSession, player: UnoPlayer) { + if (!game.started) { + player.socket.send(JSON.stringify({"notify":"game not started"})) + } else { + let ret = logic.uno(game, player) + player.socket.send(JSON.stringify({ + notify: ret[1], + warning: ret[0], + })) + game.clientUpdatePlayer(player); + } +} \ No newline at end of file diff --git a/UNO-Backend/src/gameLogic/UiVote.ts b/UNO-Backend/src/gameLogic/UiVote.ts new file mode 100644 index 0000000..8e0cf1e --- /dev/null +++ b/UNO-Backend/src/gameLogic/UiVote.ts @@ -0,0 +1,94 @@ +import WebSocket from 'ws'; +import { UnoSession, UnoPlayer } from './UnoSession'; + +export interface UIVoteQuestion { + question: string; + options: string[]; + counter?: number[]; +} + +export class UIVote { + duration: number; + questions: UIVoteQuestion[]; + game: UnoSession; + title: string; + votedPlayers: String[]; + stopTimer; + + constructor(game: UnoSession, title:string, duration: number){ + this.duration = duration; + this.game = game; + this.title = title; + this.questions = []; + this.stopTimer = undefined; + this.votedPlayers = []; + } + + public addQuestion(question: string, options: string[]){ + let countArray = []; + options.forEach(el => countArray.push(0)); + this.questions.push({ + question: question, + options: options, + counter: countArray, + }) + } + + public startVote(callback){ + [...this.game.players, ...this.game.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({ + "vote": this.title, + "questions":this.questions, + "time": this.duration + })) + } + }) + this.stopTimer = setTimeout(()=>this.endVote(callback), this.duration); + } + + private endVote(callback){ + [...this.game.players, ...this.game.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({"endvote":""})) + } + }) + callback(this.questions); + } + + private broadcastResults(){ + [...this.game.players, ...this.game.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({"voteres":this.questions})) + } + }) + } + + public abortVote(){ + clearTimeout(this.stopTimer); + [...this.game.players, ...this.game.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({"endvote":""})) + } + }) + } + + public voteEvent(id,player:UnoPlayer){ + if (this.votedPlayers.filter(el => el == player.userId).length == 0){ + this.questions[0].counter[id - 1] ++; + this.broadcastResults(); + player.socket.send(JSON.stringify({ + notify: "Stimme abgegeben", + warning: true, + })) + this.votedPlayers.push(player.userId) + }else{ + player.socket.send(JSON.stringify({ + notify: "Stimme bereits abgegeben", + warning: false, + })) + } + } + + +} \ No newline at end of file diff --git a/UNO-Backend/src/gameLogic/UnoChat.ts b/UNO-Backend/src/gameLogic/UnoChat.ts new file mode 100644 index 0000000..9d32f67 --- /dev/null +++ b/UNO-Backend/src/gameLogic/UnoChat.ts @@ -0,0 +1,29 @@ +import * as Log from './../util/logging.js' +import { UnoSession, UnoPlayer, } from './UnoSession.js'; + +export class UnoChat { + private chatHistory: string[]; + private game: UnoSession; + constructor(game: UnoSession) { + this.chatHistory = []; + this.game = game; + } + + public writeMessage (msg:string ) { + let now = new Date(); + let timestamp = now.getHours() + ":" + now.getMinutes(); + const line = "" + timestamp + " " + msg + "
" + Log.logChat(line); + + [...this.game.players, ...this.game.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({ + "chat": line + })) + } + }) + + } + +} + diff --git a/UNO-Backend/src/gameLogic/UnoRules.ts b/UNO-Backend/src/gameLogic/UnoRules.ts new file mode 100644 index 0000000..2b147b8 --- /dev/null +++ b/UNO-Backend/src/gameLogic/UnoRules.ts @@ -0,0 +1,336 @@ +import * as log from '../util/logging.js' +import { UnoSession, UnoCard, UnoPlayer, } from './UnoSession.js'; +import { UIVote } from './UiVote.js'; + +export function playCard(game: UnoSession, player: UnoPlayer, card: UnoCard, double?: UnoCard){ + log.logNotice("Player " + player.nickname + " (" + player.userId + ") plays card"); + let topCard = game.cardsPlayed[game.cardsPlayed.length - 1]; + //pr�fen ob spieler dran ist + if (!(game.currentPlayer == player.userId)) { + if(game.extraRules.sw_jumpin){ + //zwischenwerfen + if ((topCard.number == card.number) && (topCard.color == card.color)) { + game.cardsPlayed.push(card); + game.clientRemoveCard(player,card); + player.hand = player.hand.filter(el => el.id != card.id); + let returnString = "Karte zwischengespielt"; + game.unoChat.writeMessage(player.nickname + ' hat eine ' +formatCard(card)+ ' zwischengespielt.'); + if (player.hand.length == 1 && !player.uno) { + drawPlayer(game, 1); + returnString = "Karte zwischengespielt. Da du nicht UNO gesagt hast, musstest du eine Karte ziehen." + log.logNotice("Player " + player.nickname + " (" + player.userId + ") did not mention UNO. Add +2 Cards"); + game.unoChat.writeMessage(player.nickname + " hat nicht UNO! gesagt und muss eine Karte ziehen."); + } + if (player.hand.length == 0 && player.uno) { + finishGame(game, player); + } + return [true, returnString] + }else{ + return [false, "Du kannst diese Karte nicht einwerfen"] + } + }else{ + return [false, "Du bist nicht dran"] + } + } + //pr�fen ob Karte passt + if ((topCard.color == "select") || (topCard.color == card.color) || (topCard.number == card.number) || card.number == 'wild' || card.number == 'wild+4' || game.testmode) { + //kann gelegt werden + game.cardsPlayed.push(card); + game.clientRemoveCard(player,card); + player.hand = player.hand.filter(el => el.id != card.id); + let returnString = "Karte gespielt"; + //double akti + + if(game.extraRules.sw_double && double){ + game.cardsPlayed.push(double); + game.clientRemoveCard(player,double); + player.hand = player.hand.filter(el => el.id != double.id); + returnString = "Doppelt gespielt"; + game.unoChat.writeMessage(player.nickname + ' hat zwei ' + formatCard(card)+ ' gelegt.'); + + } + if(game.extraRules.sw_double && !double){ + game.unoChat.writeMessage(player.nickname + ' hat eine ' + formatCard(card)+ ' gelegt.'); + } + + //uno pr�fen + + if (player.hand.length == 1 && !player.uno) { + drawPlayer(game, 1); + returnString = "Karte gespielt. Da du nicht UNO gesagt hast, musstest du eine Karte ziehen." + log.logNotice("Player " + player.nickname + " (" + player.userId + ") did not mention UNO. Add +2 Cards"); + game.unoChat.writeMessage(player.nickname + " hat nicht UNO! gesagt und muss eine Karte ziehen."); + } + if (player.hand.length == 0 ) { + finishGame(game, player); + } + //n�chster spieler + advancePlayer(game, card.number == 'reverse', card.number == 'skip'); + + if (card.number == '+2') { + drawPlayer(game, 2); + game.players.forEach((obj, ix) => { + if (obj.userId == game.currentPlayer) { game.unoChat.writeMessage(obj.nickname + " muss zwei Karten ziehen und setzt eine Runde aus.");} + }); + advancePlayer(game, false, false); + } + if (card.number == 'wild+4') { + drawPlayer(game, 4); + game.players.forEach((obj, ix) => { + if (obj.userId == game.currentPlayer) { game.unoChat.writeMessage(obj.nickname + " muss vier Karten ziehen und setzt eine Runde aus."); } + }); + advancePlayer(game, false, false); + } + game.clientUpdatePlayedCard(); + return [true, returnString] + } else { + return [false, "Du kannst diese Karte hier nicht legen"] + } + +} + +export function skip (game: UnoSession, player: UnoPlayer){ + log.logNotice("Player " + player.nickname + " (" + player.userId + ") skips card"); + game.unoChat.writeMessage(player.nickname + " setzt aus."); + //pr�fen ob spieler dran ist + if (!(game.currentPlayer == player.userId)) { + return [false, "Du bist nicht dran"] + } + advancePlayer(game, false, false); + return [true, "Ausgesetzt"] +} + +export function pull(game: UnoSession, player: UnoPlayer) { + log.logNotice("Player " + player.nickname + " (" + player.userId + ") pulls card"); + //pr�fen ob spieler dran ist + if (!(game.currentPlayer == player.userId)) { + return [false, "Du bist nicht dran"] + } + if (player.cardDrawn) { + return [false, "Du hast bereits eine Karte gezogen"] + } + game.unoChat.writeMessage(player.nickname + " zieht eine Karte."); + player.cardDrawn = true; + let card = game.cardsMixed[game.cardsMixed.length - 1]; + game.cardsMixed.pop(); + log.logNotice("Pulled Card form Stack. Remaining: " + game.cardsMixed.length); + game.clientAddCard(player,card); + player.hand.push(card); + player.uno = false; + player.socket.send(JSON.stringify({ + changprop:"canskip", + value: true, + })) + return [true, "Karte gezogen"] +} + +export function uno(game: UnoSession, player: UnoPlayer) { + log.logNotice("Player " + player.nickname + " (" + player.userId + ") says UNO"); + game.unoChat.writeMessage(player.nickname + " sagt UNO!"); + //pr�fen ob spieler dran ist + if (!(game.currentPlayer == player.userId)) { + return [false, "Du bist nicht dran"] + } + player.uno = true; + return [true, "UNO!"] +} + +export function leave(game: UnoSession, player: UnoPlayer) { + log.logNotice("Player " + player.nickname + " (" + player.userId + ") leaves"); + game.unoChat.writeMessage(player.nickname + " verl�sst das Spiel."); + player.hand.forEach((obj) => { + game.cardsMixed = [obj, ...game.cardsMixed]; + }); + if (player.userId == game.currentPlayer) { + advancePlayer(game, false, false); + } + player.socket.send(JSON.stringify({kick:"Spiel verlassen."})); + game.players = game.players.filter(el => el.userId != player.userId); + checkRestart(game); +} + +export function onStart(game:UnoSession){ + + if(game.extraRules.sw_sleep){ + game.startTimer(8,() => { + let index = 0; + game.players.forEach((obj, ix) => { + if (obj.userId == game.currentPlayer) {index = ix;} + }); + drawPlayer(game,1); + game.clientNotify(game.players[index],'Du hast gepennt und musst einer Karte ziehen!',true); + game.clientPlaySound(game.players[index],'../assets/skip.wav') + game.unoChat.writeMessage(game.players[index].nickname + "hat gepennt und muss eine Karte ziehen"); + advancePlayer(game,false,false) + }) + } +} + +export function advancePlayer(game:UnoSession, change, skip) { + + let index = 0; + game.players.forEach((obj, ix) => { + if (obj.userId == game.currentPlayer) {index = ix;} + }); + if(game.extraRules.sw_sleep){ + game.startTimer(8,() => { + let index_sub = 0; + game.players.forEach((obj, ix) => { + if (obj.userId == game.currentPlayer) {index_sub = ix;} + }); + drawPlayer(game,1); + game.clientNotify(game.players[index_sub],'Du hast gepennt und musst einer Karte ziehen!',true); + game.clientPlaySound(game.players[index_sub],'../assets/skip.wav') + game.unoChat.writeMessage(game.players[index_sub].nickname + "hat gepennt und muss eine Karte ziehen"); + advancePlayer(game,false,false) + }) + } + game.players[index].cardDrawn = false; //temp werte resetten + if (change) { game.direction = game.direction * -1 } + let step = game.direction; + if (skip) { step = step * 2 }; + let newIndex = index + step; + if (newIndex < 0) { newIndex += game.players.length } + if (newIndex > (game.players.length - 1)) { newIndex -= game.players.length } + console.log(newIndex) + game.currentPlayer = game.players[newIndex].userId; + game.clientUpdateCurrentPlayer(); + checkStack(game); + checkRestart(game); +} + +function checkRestart(game:UnoSession) { + if (game.players.length == 1) { + log.logNotice("One Player Left. Restarting in 5s"); + game.unoChat.writeMessage("Ein Spieler verbleibend. Neustart in 5 Sekunden."); + if(game.activeVote){game.activeVote.abortVote();} + if(game.extraRules.sw_sleep){ + game.stopTimer(); + } + setTimeout(() => { game.restartGame() }, 5000); + } +} + + +function drawPlayer(game:UnoSession, amount) { + let index = 0; + game.players.forEach((obj, ix) => { + if (obj.userId == game.currentPlayer) { index = ix; } + }); + //draw for player + for (let i = 0; i < amount; i++) { + let card = game.cardsMixed[game.cardsMixed.length - 1]; + game.cardsMixed.pop(); + log.logNotice("Pulled Card form Stack. Remaining: " + game.cardsMixed.length); + game.players[index].hand.push(card); + game.clientAddCard(game.players[index],card); + } + game.clientUpdatePlayer(game.players[index]); + return [true, ""] +} + +function finishGame(game:UnoSession, player: UnoPlayer) { + + game.players.forEach((obj, ix) => { + if (obj.userId == player.userId) { + //TODO: Score berechnen + player.score += getScore(game,player); + game.clientRemovePlayer(player) + game.clientUpdateWinner(); + game.winners.push(obj) + game.unoChat.writeMessage(obj.nickname + " hat die Runde beendet."); + + } + }); + game.clientRemovePlayer(player); + game.clientUpdateWinner(); + game.players = game.players.filter(el => el.userId != player.userId); + + voteEarlyFinish(game); +} + +function checkStack(game:UnoSession) { + //remix stack + if (game.cardsPlayed.length > 10) { + log.logNotice("Remix played cards"); + game.unoChat.writeMessage("Karten werden neu gemischt."); + for (let i = 0; i < game.cardsPlayed.length - 2; i++) { + let card = game.cardsPlayed[i]; + game.cardsMixed.push(game.cardsPlayed[i]); + console.log(game.cardsMixed.length) + + } + game.cardsPlayed = [game.cardsPlayed[game.cardsPlayed.length - 1]]; + + game.cardsMixed.sort(() => Math.random() - 0.5); + game.cardsMixed.sort(() => Math.random() - 0.5); + game.cardsMixed.sort(() => Math.random() - 0.5); + } + +} + +function getScore(game:UnoSession, player: UnoPlayer) { + let score = 0; + game.players.forEach(pl => { + if (pl.userId != player.userId && game.winners.length == 0) { + pl.hand.forEach(card => { + switch (card.number) { + case 'wild': + score += 50; + break; + case 'wild+4': + score += 50; + break; + case '+2': + score += 20; + break; + case 'reverse': + score += 20; + break; + case 'skip': + score += 20; + break; + default: + break; + } + }) + } + }) + return score; +} + +function voteEarlyFinish(game: UnoSession){ + game.activeVote = new UIVote(game,'Vorzeitig beenden',8000); + game.activeVote.addQuestion("Soll die Runde vorzeitig beendet werden?",["Ja","Nein"]); + game.activeVote.startVote((values)=>{ + console.log("Vote results", values); + if (values[0].counter[0] > values[0].counter[1]){ + game.unoChat.writeMessage("Abstimmung erfolgreich. Nächste Runde wird gestartet.") + game.restartGame(); + } + }) +} + +function formatCard(card: UnoCard){ + let color = '' + let number = '' + switch(card.color){ + case 'red': color='Rote'; break; + case 'blue': color='Blaue'; break; + case 'green': color='Grüne'; break; + case 'yellow': color='Gelbe'; break; + } + + switch(card.number){ + case '+2': number='Zieh Zwei Karte'; break; + case 'reverse': number='Retoure Karte'; break; + case 'skip': number='Aussetzen Karte'; break; + case 'wild': number='Farbwahlkarte'; break; + case 'wild+4': number='Zieh Vier Farbwahlkarte'; break; + default: number = card.number.toString(); + } + + return color + ' ' + number + +} + diff --git a/UNO-Backend/src/gameLogic/UnoSession.ts b/UNO-Backend/src/gameLogic/UnoSession.ts new file mode 100644 index 0000000..ee3d834 --- /dev/null +++ b/UNO-Backend/src/gameLogic/UnoSession.ts @@ -0,0 +1,532 @@ +//imports +import * as log from '../util/logging.js' +import * as resp from '../interface/rest/response.js' +import { mkstring } from '../util/token.js' +import * as logic from './UnoRules.js' +import { UnoChat } from './UnoChat.js' +import { UnoSockets } from './UnoSocket.js' +import WebSocket from 'ws'; + +import jsonCards from '../../config/cards.json' +import jsonConf from '../../config/game.json' +import { shuffle } from '../util/shuffle.js' +import { UIVote } from './UiVote.js' + +export interface UnoCard { + id: number; + color: string; + number: string; + tx: string; +} + +export interface UnoPlayer { + nickname: string; + lastseen: Date; + userId: string; + ready: boolean; + uno: boolean; + cardDrawn: boolean; + score: number; + rounds: number; + hand: UnoCard[]; + socket: WebSocket; +}1 + +export interface UnoExtraRules { + sw_stacking: boolean; + sw_jumpin: boolean; + sw_double: boolean; + sw_sleep: boolean; + sw_seveno: boolean; +} + +export class UnoSession { + sessionID: number; //SitzungsID + players: UnoPlayer[]; //alle Spielerobjekte + winners: UnoPlayer[]; //fertige spieler + cardsPlayed: UnoCard[]; //gespielte Karten Satpel + cardsMixed: UnoCard[]; //nachziehkarten Stapel + started: boolean; //spiel gestartet + password: string; //passwort f�r den zutritt + currentPlayer: string; //aktueller spieler + direction: number; //spielrichtung (1=vor, -1=zur�ck, 0=nicht gestartet) + startcards: number; //anzahl der maximalen karten + maxPlayers: number; //maximale spieleranzahl + serverName: string; //serever Name + unoChat: UnoChat; + sockets: UnoSockets; + lastPlayedBy: UnoPlayer; + testmode: boolean; + activeVote: UIVote; + + sessionId: string; + canDelete: boolean; + + extraRules: UnoExtraRules; + + sleepTimer; + sleepValue; + + stackSize: number; //anzahl der Karten die genzpgen werden müssen + + private checkTimer; + + constructor(sessionId,custom: any){ + this.extraRules = { + sw_stacking: custom.sw_stacking , + sw_jumpin: custom.sw_jumpin , + sw_double: custom.sw_double , + sw_sleep: custom.sw_sleep , + sw_seveno: custom.sw_seveno , + } + console.log(custom) + this.players= []; + this.winners= []; + this.unoChat= new UnoChat(this); + this.sockets= undefined; + this.sessionId= sessionId; + //konfigurationsdate laden + log.logNotice("Spiel Starten. Lade Konfigurationsdatei..."); + let t_this = jsonConf; + this.password = t_this.password; + this.startcards = custom.startcards || t_this.startCards; + this.maxPlayers = t_this.maxPlayers; + this.serverName = t_this.serverName; + this.testmode = t_this.testMode; + this.sockets = new UnoSockets(this); + this.activeVote = undefined; + this.canDelete = false; + this.initLobby(); + //kick inactive + this.checkTimer = setInterval(()=>{ + [...this.players, ...this.winners].forEach(el =>{ + var dif = ((new Date()).getTime()- el.lastseen.getTime())/1000; + if(!this.started && dif > 10){ + this.kickUser(el, "10 Sekunden Inaktiv") + }else if(this.started && dif > 60){ + this.kickUser(el, "60 Sekunden Inaktiv") + }else if(this.started && dif > 29 && dif < 31){ + this.unoChat.writeMessage(el.nickname + " wird in 30 Sekunden wegen Inaktivität gekickt.") + } + else if(this.started && dif > 44 && dif < 46){ + this.unoChat.writeMessage(el.nickname + " wird in 15 Sekunden wegen Inaktivität gekickt.") + } + }) + if ([...this.players, ...this.winners].length == 0){ + log.logInfo('Lobby empty. Closing...'); + this.canDelete = true; + this.deleteSession(); + } + },1000); + } + + public deleteSession(){ + [...this.players, ...this.winners].forEach(el => el.socket.close()) + clearInterval(this.sockets.pingTimer) + clearInterval(this.checkTimer) + this.stopTimer(); + } + + private initLobby() { + this.cardsPlayed = []; //gespielte Karten Satpel + this.cardsMixed = []; //nachziehkarten Stapel + this.started = false; //spiel gestartet + this.currentPlayer = ""; + this.direction = 0; + this.stackSize = 0; + //Karten Laden + log.logNotice("Spiel Starten. Lade Karten..."); + let cards = jsonCards.cards; + let counter = 0; + cards.forEach(el => { + for (let i = 0; i < el.count; i++) { + this.cardsMixed.push({ + id: counter, + color: el.color, + number: el.number, + tx: el.tx + }); + counter++; + } + }); + shuffle(this.cardsMixed); //Karten mischen + shuffle(this.cardsMixed); //Karten mischen + shuffle(this.cardsMixed); //Karten mischen + shuffle(this.cardsMixed); //Karten mischen + log.logNotice(this.cardsMixed.length.toString() + " Karten geladen."); + this.clientUpdateStatus(); + } + + public restartGame() { + this.players = [...this.players, ...this.winners]; //alle Spielerobjekte + this.winners = []; + shuffle(this.players); + this.players.forEach(player => { + player.hand = []; //übrige karten löschen + player.ready = false; + player.uno = false; + player.cardDrawn = false; + player.rounds++; + + }) + this.initLobby(); + this.clientUpdateHands(); + this.clientUpdateAllPlayers(); + + } + + public playerJoin(nickname: string):any[] { + if (this.started) { + return [false,"Das Spiel wurde bereits gestartet."] + } else { + if (!nickname) { + log.logError("User tried to connect without Nickname") + return [false,"Du musst einen benutzernamen angeben."] + + } + //wenn sonst soweit alles passt + const playerObj: UnoPlayer = { + nickname: nickname, + lastseen: new Date(), + userId: mkstring(20), + ready: false, + uno: false, + cardDrawn: false, + score: 0, + rounds: 1, + hand: [], + socket: undefined, + }; + + this.players.push(playerObj); + this.clientUpdatePlayer(playerObj); + log.logNotice("Player " + nickname + " joined with id " + playerObj.userId); + this.unoChat.writeMessage(nickname + " ist dem Spiel beigetreten"); + + return [true,{ + userId: playerObj.userId + }] + } + } + + public checkReady() { + let all = 0; + let ready = 0; + this.players.forEach(function (obj, ix) { + all++; + if (obj.ready) { ready++; } + }); + log.logNotice(ready + "/" + all + " Players are ready"); + this.unoChat.writeMessage(ready + "/" + all + " spieler sind Bereit"); + if (all == ready && all > 1) { + log.logNotice("Start Game"); + this.startGame(); + logic.onStart(this); + } + } + + public startGame() { + this.started = true; + log.logNotice("Starting Game. Dealing Cards"); + for (let player_ix = 0; player_ix < this.players.length; player_ix++) { + let pObj = this.players[player_ix]; + pObj.hand = []; + for (let i = 0; i < this.startcards; i++) { + pObj.hand.push(this.pullCardFromStack()); + } + this.clientUpdatePlayer(pObj); + } + this.cardsPlayed.push(this.pullCardFromStack()); + + log.logNotice("Cards dealt"); + + this.unoChat.writeMessage("Die Runde wurde gestartet"); + this.currentPlayer = this.players[0].userId; + this.direction = 1; + this.clientUpdatePlayedCard() + this.clientUpdateCurrentPlayer(); + this.clientUpdateStatus(); + this.clientUpdateHands(); + } + + private pullCardFromStack() { + let card = this.cardsMixed[this.cardsMixed.length - 1]; + this.cardsMixed.pop(); + log.logNotice("Pulled Card form Stack. Remaining: " + this.cardsMixed.length); + return card; + } + + public register_socket(socket: WebSocket, id: string){ + this.sockets.register_socket(socket,id); + } + + + public initClient(player: UnoPlayer) { + let respObj: any = {}; + //add hands + let players_public = [] + let players_winners = [] + this.players.forEach((obj) => { + players_public.push(this.getPlayerMeta(obj)); + }); + this.winners.forEach((obj) => { + players_winners.push(this.getPlayerMeta(obj)); + }); + + respObj.hand = player ? player.hand : []; + respObj.serverName = this.serverName; + respObj.cardsMixed = this.cardsMixed.length; + respObj.cardsPlayed = this.cardsPlayed; + respObj.started = this.started; + respObj.direction = this.direction; + respObj.currentPlayer = this.currentPlayer; + respObj.players = players_public; + respObj.winners = players_winners; + respObj.extraRules = this.extraRules; + respObj.extraRules.cards = this.startcards; + respObj.stack = this.stackSize; + player.socket.send(JSON.stringify({ + initClientData: respObj + })) + } + + public clientHearbeat(player: UnoPlayer){ + player.lastseen = new Date(); + } + + public findPlayerIndex(id) { + let index = 0; + this.players.forEach((obj, ix) => { + if (obj.userId == id) { + index = ix; + } + }); + return index; + } + + public clientUpdatePlayer(player: UnoPlayer){ //modify or add player + [...this.players, ...this.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({ + player: "modify", + data: this.getPlayerMeta(player) + })) + } + }) + } + + public clientRemovePlayer(player: UnoPlayer){ //remove player + [...this.players, ...this.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({ + player: "remove", + data: this.getPlayerMeta(player) + })) + } + }) + } + + private getPlayerMeta(player: UnoPlayer){ + return { + nickname: player.nickname, + lastseen: player.lastseen, + userId: player.userId, + ready: player.ready, + cards: player.hand ? player.hand.length : 0, + uno: player.uno, + score: player.score, + rounds: player.rounds, + } + } + + public clientUpdateWinner(){ //modify or add player + let players_winners = [] + this.winners.forEach((obj) => { + players_winners.push(this.getPlayerMeta(obj)); + }); + [...this.players, ...this.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + + el.socket.send(JSON.stringify({ + player: "winners", + data: players_winners + })) + } + }) + } + + public clientUpdateAllPlayers(){ + let players_public = [] + let players_winners = [] + this.players.forEach((obj) => { + players_public.push(this.getPlayerMeta(obj)); + }); + this.winners.forEach((obj) => { + players_winners.push(this.getPlayerMeta(obj)); + }); + [...this.players, ...this.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({ + player: "all", + players: players_public, + winners: players_winners, + })) + } + }) + } + + public clientUpdatePlayedCard(){ + [...this.players, ...this.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({ + gameUpdate: "playedStack", + card: this.cardsPlayed, + })) + } + }) + } + + public clientUpdateCurrentPlayer(){ + [...this.players, ...this.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({ + gameUpdate: "currentPlayer", + currentplayer: this.currentPlayer, + direction: this.direction, + })) + } + }) + } + + public clientUpdateStatus(){ + [...this.players, ...this.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({ + gameUpdate: "status", + started: this.started + })) + } + }) + } + + public clientUpdateHands(){ + this.players.forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({ + hand: "all", + cards: el.hand + })) + } + }) + } + + public clientAddCard(player: UnoPlayer, card: UnoCard){ //modify or add player + if (player.socket && player.socket?.OPEN){ + player.socket.send(JSON.stringify({ + hand: "add", + card: card + })) + } + } + + public clientRemoveCard(player: UnoPlayer, card: UnoCard){ //modify or add player + if (player.socket && player.socket?.OPEN){ + player.socket.send(JSON.stringify({ + hand: "remove", + card: card + })) + } + } + + public kickUser(player: UnoPlayer, reason: string){ + log.logNotice("Player " + player.nickname + " (" + player.userId + ") kicked for reason : " + reason); + this.unoChat.writeMessage(player.nickname + " wurde gekickt. Grund: " + reason); + if (player.socket && player.socket?.OPEN){ + player.socket.send(JSON.stringify({kick:reason})); + } + if(this.started){ + this.cardsMixed = [...player.hand, ...this.cardsMixed]; + if (player.userId == this.currentPlayer) { + logic.advancePlayer(this, false, false); + } + } + this.winners = this.winners.filter(el2 => el2.userId != player.userId); + this.players = this.players.filter(el2 => el2.userId != player.userId); + this.clientUpdateAllPlayers(); + } + + public startTimer(duration: number, event: Function){ + this.stopTimer(); + this.sendTimer(duration); + this.sleepValue = duration; + this.sleepTimer = setInterval(()=>{ + this.sleepValue --; + this.sendTimer(this.sleepValue); + if (this.sleepValue == -1){ + this.stopTimer(); + event(); + } + },1000) + } + + public stopTimer(){ + if (this.sleepTimer){ + clearInterval(this.sleepTimer) + } + } + + private sendTimer(value: number){ + [...this.players, ...this.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({ + timer: value, + })) + } + }) + } + + public clientPlaySound(player: UnoPlayer, sound: string){ + if (player.socket && player.socket?.OPEN){ + player.socket.send(JSON.stringify({ + sound: sound, + })) + } + } + + public clientNotify(player: UnoPlayer, message: string, failed: boolean){ + if (player.socket && player.socket?.OPEN){ + player.socket.send(JSON.stringify({ + notify: message, + warning: !failed, + })) + } + } + + public addStackSize(value: number){ + this.stackSize += value; + [...this.players, ...this.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({ + stack: this.stackSize + })) + } + }) + this.sendStackSize(); + } + + public resetStackSize(){ + this.stackSize = 0; + this.sendStackSize(); + } + + private sendStackSize(){ + [...this.players, ...this.winners].forEach(el =>{ + if (el.socket && el.socket?.OPEN){ + el.socket.send(JSON.stringify({ + stack: this.stackSize + })) + } + }) + } + +} diff --git a/UNO-Backend/src/gameLogic/UnoSocket.ts b/UNO-Backend/src/gameLogic/UnoSocket.ts new file mode 100644 index 0000000..a0b277d --- /dev/null +++ b/UNO-Backend/src/gameLogic/UnoSocket.ts @@ -0,0 +1,74 @@ +import { UnoSession, UnoCard, UnoPlayer, } from './UnoSession.js'; +import { processUiEvents } from './UiEvents.js' +import * as Log from './../util/logging.js' +import WebSocket from 'ws'; + +export class UnoSockets { + private game:UnoSession; + pingTimer; + + constructor(game) { + //heatbeat + this.game = game; + this.pingTimer = setInterval(()=>{ + this.game.players.forEach(el => { + if(el.socket){ + el.socket.send(JSON.stringify({ + heartbeat: true + })) + } + }) + },10000) + + } + + public register_socket(socket: WebSocket, id: string){ + let valid: boolean = false; + this.game.players.forEach((obj, ix) => { + if (obj.userId == id) { + console.log('registered socket fot ',obj.userId); + obj.socket = socket; + obj.socket.on('message', (message: string) => { + console.log('received message form:',obj.userId,'data', message); + const command = JSON.parse(message); + this.processCommand(command,obj); + }); + valid = true; + } + }); + this.game.winners.forEach((obj, ix) => { + if (obj.userId == id) { + console.log('registered socket fot ',obj.userId); + obj.socket = socket; + obj.socket.on('message', (message: string) => { + console.log('received message form:',obj.userId,'data', message); + const command = JSON.parse(message); + this.processCommand(command,obj); + }); + valid = true; + } + }); + if (!valid){ + socket.send(JSON.stringify({kick:"Ungültige session."})); + } + } + + private processCommand(command: any, player: UnoPlayer){ + switch(command.action){ + case 'uibutton': + processUiEvents(this.game,player,command) + break; + case 'initData': + this.game.initClient(player); + break; + case 'heartbeat': + this.game.clientHearbeat(player); + break; + default: + //none + } + if (command.vote){ + if(this.game.activeVote){this.game.activeVote.voteEvent(command.vote, player)} + } + } +} \ No newline at end of file diff --git a/UNO-Backend/src/interface/rest/response.ts b/UNO-Backend/src/interface/rest/response.ts new file mode 100644 index 0000000..9a35e33 --- /dev/null +++ b/UNO-Backend/src/interface/rest/response.ts @@ -0,0 +1,10 @@ +export function resp (req, res, success, data, code) { + res.status(code); + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + success: success, + data: data, + request_time: (new Date()).toUTCString(), + api: "UNOv1" + })); +}; diff --git a/UNO-Backend/src/lobby/UnoLobby.ts b/UNO-Backend/src/lobby/UnoLobby.ts new file mode 100644 index 0000000..9703d65 --- /dev/null +++ b/UNO-Backend/src/lobby/UnoLobby.ts @@ -0,0 +1,76 @@ +import { UnoSession } from '../gameLogic/UnoSession.js'; +import { mkstring } from '../util/token.js'; +import WebSocket from 'ws'; + +export interface UnoLobbyProperties { + serverName: string; + startcards: number; + +} + +export interface UnoLobbyElement { + session?: UnoSession; + sessionID: string; + properties: UnoLobbyProperties; + +} + +export class UnoLobby { + + sessions: UnoLobbyElement[]; + + constructor(){ + this.sessions = [] + setInterval(()=>{ + this.sessions = this.sessions.filter(el => !el.session.canDelete) + },500) + } + + public createLobby(properties: UnoLobbyProperties){ + const sId: string = mkstring(30); + this.sessions.push({ + sessionID: sId, + properties: properties, + session: new UnoSession(sId,properties) + }) + return sId; + } + + public getLobbies(){ + let list = [ ]; + this.sessions.forEach(el => { + list.push({ + id: el.sessionID, + properties: el.properties, + status: el.session.started, + players: [...el.session.players, ...el.session.winners].length + }) + }) + return list; + } + + public joinLobby(nickname: string, lobbyId: string){ + let returnval = [false,"Die Lobby wurde nicht gefunden."] + this.sessions.forEach(el => { + console.log(el.sessionID, lobbyId) + if(el.sessionID == lobbyId){ + returnval = el.session.playerJoin(nickname); + console.log("found",returnval) + } + }) + console.log("done",returnval) + return returnval; + + // + } + + public registerLobby(message: string, ws: WebSocket){ + const command = JSON.parse(message); + this.sessions.forEach(el => { + if(el.sessionID == command.lobby){ + console.log("found session for " + command.id + " @ " + command.lobby ) + el.session.register_socket(ws,command.id); + } + }) + } +} \ No newline at end of file diff --git a/UNO-Backend/src/util/logging.ts b/UNO-Backend/src/util/logging.ts new file mode 100644 index 0000000..6af6758 --- /dev/null +++ b/UNO-Backend/src/util/logging.ts @@ -0,0 +1,36 @@ +export function logInfo(text) { + var tmp = "\x1b[32m[Info] \x1b[0m" + text; + console.log(tmp) +} + +export function logWarning(text) { + var tmp = "\x1b[33m[Warn] \x1b[0m" + text; + console.log(tmp) +} + + +export function logError(text) { + var tmp = "\x1b[31m[Error] \x1b[0m" + text; + console.log(tmp) +} + +export function logCritical(text) { + var tmp = "\x1b[4m\x1b[31m[Critical]\x1b[0m \x1b[4m" + text + "\x1b[0m"; + console.log(tmp) +} + +export function logNotice (text) { + var tmp = "\x1b[36m[Notice] \x1b[0m" + text; + console.log(tmp) +} + +export function logDebug(text) { + var tmp = "\x1b[36m[DEBUG] \x1b[0m" + text; + console.log(tmp) +} + +export function logChat (text) { + var tmp = "\x1b[35m[CHAT] \x1b[0m" + text; + console.log(tmp) +} + diff --git a/UNO-Backend/src/util/shuffle.ts b/UNO-Backend/src/util/shuffle.ts new file mode 100644 index 0000000..bb98088 --- /dev/null +++ b/UNO-Backend/src/util/shuffle.ts @@ -0,0 +1,3 @@ +export function shuffle(array) { + array.sort(() => Math.random() - 0.5); +} \ No newline at end of file diff --git a/UNO-Backend/src/util/token.ts b/UNO-Backend/src/util/token.ts new file mode 100644 index 0000000..a3631a1 --- /dev/null +++ b/UNO-Backend/src/util/token.ts @@ -0,0 +1,9 @@ +export function mkstring(length) { + var result = ''; + var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var charactersLength = characters.length; + for (var i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} \ No newline at end of file diff --git a/UNO-Backend/tsconfig - Copy.json b/UNO-Backend/tsconfig - Copy.json new file mode 100644 index 0000000..2fa2ff3 --- /dev/null +++ b/UNO-Backend/tsconfig - Copy.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + + "moduleResolution": "node", + "resolveJsonModule": true, + "importHelpers": true, + "target": "es2015" + }, + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/UNO-Backend/tsconfig.json b/UNO-Backend/tsconfig.json new file mode 100644 index 0000000..6d63edb --- /dev/null +++ b/UNO-Backend/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "esModuleInterop": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "importHelpers": true, + "target": "es2015", + "allowSyntheticDefaultImports": true + }, + "exclude": [ + "node_modules", + "dist" + ] +}