initial merge of both repos

This commit is contained in:
2021-02-25 23:11:23 +01:00
commit e1ec6b2db9
138 changed files with 4282 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@@ -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

16
UNO-Angular/.editorconfig Normal file
View File

@@ -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

46
UNO-Angular/.gitignore vendored Normal file
View File

@@ -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

27
UNO-Angular/README.md Normal file
View File

@@ -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).

160
UNO-Angular/angular.json Normal file
View File

@@ -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
}
}

12
UNO-Angular/browserslist Normal file
View File

@@ -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'.

41
UNO-Angular/dockerfile Normal file
View File

@@ -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

View File

@@ -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 } }));
}
};

View File

@@ -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));
});
});

View File

@@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl) as Promise<unknown>;
}
getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText() as Promise<string>;
}
}

View File

@@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

32
UNO-Angular/karma.conf.js Normal file
View File

@@ -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
});
};

60
UNO-Angular/nginx.conf Normal file
View File

@@ -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;
}
}

67
UNO-Angular/package.json Normal file
View File

@@ -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"
}
}

18
UNO-Angular/proxy.json Normal file
View File

@@ -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
}
}

View File

@@ -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 { }

View File

@@ -0,0 +1,7 @@
<nav class="navbar navbar-light bg-light">
<a class="navbar-brand" href="#"><b>UNO.DENNISGUNIA.DE</b> - Version 1.0</a>
</nav>
<router-outlet #outlet="outlet"></router-outlet>

View File

@@ -0,0 +1,3 @@
.navbar{
opacity: 0.7;
}

View File

@@ -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'];
}
}

View File

@@ -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 { }

View File

@@ -0,0 +1,107 @@
<div>
<div class="card lobby-card">
<div class="card-header">
<h1><b>UNO Sitzungen</b> - {{nick}}</h1>
<hr />
<button class="btn btn-success btn-sm" (click)="prepareModal()" data-toggle="modal" data-target="#basicExampleModal" style="margin-right: 1rem;"><i class="fas fa-magic mr-1"></i> Sitzung erstellen</button>
<button class="btn btn-success btn-sm" style="margin-right: 1rem;"><i class="fas fa-random mr-1"></i> Zufäliger Sitzung beitreten</button>
<button class="btn btn-primary btn-sm" (click)="loadLobbies()"><i class="fas fa-sync mr-1"></i> Übersicht Aktualisieren</button>
<button class="btn btn-warning btn-sm" routerLink="/start" style="position:absolute; right: 1.25rem;"><i class="fas fa-edit mr-1"></i>Benutzernamen ändern</button>
</div>
<ul class="list-group list-group-flush" *ngIf="lobbies.length > 0">
<li class="list-group-item" *ngFor="let lobby of lobbies">
<div class="container">
<div class="row">
<div class="col-sm btn-align" style="font-weight: 500;">
{{lobby.properties.serverName}}
<span *ngIf="lobby.status">(Im Spiel)</span>
<span *ngIf="!lobby.status">(in Lobby)</span>
</div>
<div class="col-sm btn-align">
{{lobby.players}} Spieler
</div>
<div class="col-sm btn-align">
<ul>
<li *ngIf="!(lobby.properties.startcards == 7)">{{lobby.properties.startcards}} Karten zu Spielbeginn</li>
<li *ngIf="!(lobby.properties.sw_stacking == 7)">Kumulieren</li>
<li *ngIf="!(lobby.properties.sw_jumpin == 7)">Jump-In</li>
<li *ngIf="!(lobby.properties.sw_double == 7)">Doppeln</li>
<li *ngIf="!(lobby.properties.sw_sleep == 7)">Pennen</li>
<li *ngIf="!(lobby.properties.sw_seveno == 7)">Seven-O (Sieben-Null)</li>
</ul>
</div>
<div class="col-sm">
<button class="btn btn-success btn-sm" [disabled]="lobby.status" (click)="joinGame(lobby)"><i class="fas fa-caret-square-right mr-1"></i>Spiel Beitreten</button>
</div>
</div>
</div>
</li>
</ul>
<ul class="list-group list-group-flush" *ngIf="lobbies.length == 0">
<li class="list-group-item text-muted" >
Es gibt keine aktiven Sitzungen.
</li>
</ul>
<div class="card-footer text-muted">
Programmiert von Dennis Gunia (2020) - Open Source UNO - <a href="https://www.dennisgunia.de">dennisgunia.de</a> - Repo: <a href="https://git.dennis-gunia.de/dennisgunia/UNO-Backend">git.dennis-gunia.de</a>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="basicExampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Neue Sitzung erstellen</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form [formGroup]="createForm" (ngSubmit)="createLobby()">
<div class="modal-body">
<label for="sessionname">Sitzungsname</label>
<input type="text" id="sessionname" [className]="createForm.controls.serverName.invalid ? ' form-control is-invalid' : 'form-control is-valid'" formControlName="serverName"> <br />
<label for="startcards">Karten zu beginn</label>
<input type="number" id="startcards" min="2" max="20" [className]="createForm.controls.startcards.invalid ? ' form-control is-invalid' : 'form-control is-valid'" formControlName="startcards"> <br />
<label >Sonderregeln</label>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="sw_stacking" formControlName="sw_stacking">
<label class="custom-control-label" for="sw_stacking">Kumulieren</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="sw_jumpin" formControlName="sw_jumpin">
<label class="custom-control-label" for="sw_jumpin">Jump-In</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="sw_double" formControlName="sw_double">
<label class="custom-control-label" for="sw_double">Doppeln</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="sw_sleep" formControlName="sw_sleep">
<label class="custom-control-label" for="sw_sleep">Pennen</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="sw_seveno" formControlName="sw_seveno">
<label class="custom-control-label" for="sw_seveno">Seven-O (Sieben-Null)</label>
</div>
<a href="https://www.uno-kartenspiel.de/uno-spielvarianten/" target="_blank">Infos zu den Sonderregeln</a>
<button id="closeModalButton" [hidden]="true" data-toggle="modal"
data-target="#myModal" class="btn btn-default"
data-dismiss="modal">Close</button>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger" data-dismiss="modal">Abbrechen</button>
<button type="button" type="submit" class="btn btn-success">Sitzung erstellen</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -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;
}

View File

@@ -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<BrowserComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ BrowserComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BrowserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -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', [<any>Validators.required, <any>Validators.minLength(3), <any>Validators.maxLength(20)]],
startcards: [7, [<any>Validators.required, <any>Validators.min(2), <any>Validators.max(20)]],
sw_stacking: [false, [<any>Validators.required]],
sw_jumpin: [false, [<any>Validators.required]],
sw_double: [false, [<any>Validators.required]],
sw_sleep: [false, [<any>Validators.required]],
sw_seveno: [false, [<any>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']);
}
});
}
}

View File

@@ -0,0 +1,10 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class GameStateService {
constructor() { }
}

View File

@@ -0,0 +1,202 @@
<div class="wrapper fill">
<!-- Sidebar -->
<nav id="sidebar" class="playerlist">
<h1 style="padding:0.5rem;">Hallo, {{thisPlayer.nickname}}</h1>
<div class="player card" *ngFor="let player of winnerList; let i = index">
<div class="card-body" style="padding: 0px;">
<div id="textbox">
<p class="alignleft lessspace playername">{{player.nickname}}</p>
<p class="alignright lessspace">
<span *ngIf="!(player.uno && player.cards == 1)" class="badge badge-warning">{{i + 1}}.</span>
</p>
</div><br />
<div id="textbox" class="lessspace">
<p> Spiel beendet </p>
</div>
<div id="textbox" class="lessspace" style="margin-top:-12px;">
<p class="alignleft lessspace ">Runde {{player.rounds}}</p>
<p class="alignright lessspace">{{player.score}} Punkte</p>
</div>
</div>
</div>
<hr *ngIf="winnerList?.length > 0" />
<div class="player card" *ngFor="let player of playerList" [style.background-color]="player.userId == currentPlayer.userId?'#90EE90':(nextPlayerId == player.userId ?'#FFFFE0':'#FFF')">
<div class="card-body" style="padding: 0px;">
<div id="textbox">
<p class="alignleft lessspace playername">{{player.nickname}}</p>
<p class="alignright lessspace">
<span *ngIf="player.uno && player.cards == 1" class="badge badge-danger">UNO</span>
<span *ngIf="!(player.uno && player.cards == 1)" class="badge badge-secondary">{{player.cards}}</span>
</p>
</div><br />
<div id="textbox" class="lessspace">
<p>
<span>{{player.state}}</span>
-
<span *ngIf="!gameStarted && !player.ready">In Lobby (Nicht bereit)</span>
<span *ngIf="!gameStarted && player.ready">In Lobby (Bereit)</span>
<span *ngIf="gameStarted && !(player.userId == currentPlayer.userId)">Wartet</span>
<span *ngIf="gameStarted && player.userId == currentPlayer.userId">Am Zug</span>
</p>
</div>
<div id="textbox" class="lessspace" style="margin-top:-12px;">
<p class="alignleft lessspace ">Runde {{player.rounds}}</p>
<p class="alignright lessspace">{{player.score}} Punkte</p>
</div>
</div>
</div>
<div class="footer">
<button mdbBtn type="button" *ngIf="!thisPlayer.ready" rounded="true" mdbWavesEffect class="btn btn_btm btn-success" (click)="ready()">Bereit</button>
<button mdbBtn type="button" rounded="true" mdbWavesEffect class="btn btn_btm btn-warning" (click)="leave()">Verlassen</button>
</div>
</nav>
<!-- Page Content -->
<div id="content">
<!-- We'll fill this with dummy content -->
<div class="gamefield">
<div class="footer2" [innerHTML]="chatText" #scrollMe [scrollTop]="scrollMe.scrollHeight"></div>
<div class="card countdown" *ngIf="timer >= 0">
<p><i class="fas fa-stopwatch"></i> {{timer}}</p>
</div>
<div class="card votebox" [@simpleFadeAnimation]="'in'" *ngIf="currentVote">
<div class="card-header">
{{currentVote.questions[0].question}}
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item vote-option" (click)="vote(i)" *ngFor="let opt of currentVote.questions[0].options; let i = index">
<div class="progress" [style.width]="((currentVote.questions[0].counter[i] / currentVote.all )*100) + '%'"></div>{{opt}}
</li>
</ul>
</div>
<div class="text-header" *ngIf="gameStarted">
<p class="text-header-current" *ngIf="!(currentPlayer.userId == thisPlayer.userId)">{{currentPlayer.nickname}} ist am Zug</p>
<p class="text-header-current" *ngIf="currentPlayer.userId == thisPlayer.userId">Du bist am Zug</p>
<p class="text-header-detail" *ngIf="topCard.color=='blue'">Aktuell wird <span style="color:#0095da">Blau</span> gespielt</p>
<p class="text-header-detail" *ngIf="topCard.color=='green'">Aktuell wird <span style="color:#00ab67">Grün</span> gespielt</p>
<p class="text-header-detail" *ngIf="topCard.color=='red'">Aktuell wird <span style="color:#ed1c24">Rot</span> gespielt</p>
<p class="text-header-detail" *ngIf="topCard.color=='yellow'">Aktuell wird <span style="color:#ffde00">Gelb</span> gespielt</p>
<p class="text-header-detail" *ngIf="topCard.color=='wild'">{{currentPlayer.nickname}} sucht sich eine Farbe aus</p>
</div>
<div class="text-header" *ngIf="!gameStarted">
<p class="text-header-current">Warte bis alle Spieler bereit sind</p>
</div>
<div class="play-field" *ngIf="gameStarted">
<div class="cards-center">
<button type="button" class="btn uno-card" style="margin-right:8rem;" [style.background]="'url('+ topCard.tx +')'"></button>
<button type="button" class="btn uno-card" style="margin-right:0;" [style.background]="'url(/assets/cards/000.png)'" placement="bottom" ngbTooltip="Karte ziehen" (click)="pull()"></button>
<br /><br />
<button mdbBtn type="button" rounded="true" mdbWavesEffect class="btn btn-skip btn-warning" (click)="skip()" *ngIf="(currentPlayer.userId == thisPlayer.userId) && pulled">Aussetzen</button>
<button mdbBtn type="button" rounded="true" mdbWavesEffect class="btn btn-skip btn-danger" (click)="uno()" *ngIf="(currentPlayer.userId == thisPlayer.userId)">UNO!</button>
</div>
</div>
</div>
<div class="notify-banner" *ngIf="bannerShow && !bannerError" [@simpleFadeAnimation]="'in'">
{{bannerText}}
</div>
<div class="notify-banner" *ngIf="bannerShow && bannerError" style="color:#ed1c24" [@simpleFadeAnimation]="'in'">
{{bannerText}}
</div>
<div class="footer3">
<div *ngFor="let card of handCards" [@flyInOut]="'in'">
<button *ngIf="!((card.number == 'wild+4') || (card.number == 'wild'))" type="button" class="btn uno-card uno-overlap " [style.background]="'url('+ card.tx +')'" placement="bottom" ngbTooltip="Karte legen" (click)="selectedCard=card;play(card)"></button>
<button *ngIf="card.number == 'wild+4'" type="button" class="btn uno-card uno-overlap " [style.background]="'url('+ card.tx +')'" placement="bottom" ngbTooltip="Karte legen" data-toggle="modal" data-target="#modal_plus4" (click)="selectedCard=card"></button>
<button *ngIf="card.number == 'wild'" type="button" class="btn uno-card uno-overlap " [style.background]="'url('+ card.tx +')'" placement="bottom" ngbTooltip="Karte legen" data-toggle="modal" data-target="#modal_wild" (click)="selectedCard=card"></button>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="modal_plus4" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="cards-center">
<button type="button" class="btn uno-card" style="margin-right:0;" [style.background]="'url(/assets/cards/312.png)'" placement="bottom" ngbTooltip="Blau wünschen" (click)="playwild('wild+4','blue')" data-dismiss="modal"></button>
<button type="button" class="btn uno-card" style="margin-right:0;" [style.background]="'url(/assets/cards/322.png)'" placement="bottom" ngbTooltip="Grün wünschen" (click)="playwild('wild+4','green')" data-dismiss="modal"></button>
<button type="button" class="btn uno-card" style="margin-right:0;" [style.background]="'url(/assets/cards/332.png)'" placement="bottom" ngbTooltip="Rot wünschen" (click)="playwild('wild+4','red')" data-dismiss="modal"></button>
<button type="button" class="btn uno-card" style="margin-right:0;" [style.background]="'url(/assets/cards/342.png)'" placement="bottom" ngbTooltip="Gelb wünschen" (click)="playwild('wild+4','yellow')" data-dismiss="modal"></button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="modal_wild" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="cards-center">
<button type="button" class="btn uno-card" style="margin-right:0;" [style.background]="'url(/assets/cards/311.png)'" placement="bottom" ngbTooltip="Blau wünschen" (click)="playwild('wild','blue')" data-dismiss="modal"></button>
<button type="button" class="btn uno-card" style="margin-right:0;" [style.background]="'url(/assets/cards/321.png)'" placement="bottom" ngbTooltip="Grün wünschen" (click)="playwild('wild','green')" data-dismiss="modal"></button>
<button type="button" class="btn uno-card" style="margin-right:0;" [style.background]="'url(/assets/cards/331.png)'" placement="bottom" ngbTooltip="Rot wünschen" (click)="playwild('wild','red')" data-dismiss="modal"></button>
<button type="button" class="btn uno-card" style="margin-right:0;" [style.background]="'url(/assets/cards/341.png)'" placement="bottom" ngbTooltip="Gelb wünschen" (click)="playwild('wild','yellow')" data-dismiss="modal"></button>
</div>
</div>
</div>
</div>
<!--Rules Modal-->
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" id="open_rules_modal" data-toggle="modal" data-target="#rulesModal" [hidden]="true">
Launch modal
</button>
<!-- Modal -->
<div class="modal fade" id="rulesModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Aktive Sonderregeln</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div style="text-align: left;">
<span class="rule"><b>Mehr Karten</b> Jeder hat zu Beginn {{options.cards}} statt 7 Karten. </span><br />
<span class="rule"><b>Doppeln</b> Hat ein Spieler zwei Karten von der selben Farbe und Zahl auf der Hand, so darf er diese zusammen ablegen. Dies gilt jedoch nicht für die +2- und +4-Karten. Diese müssen weiterhin nacheinander abgelegt werden. </span><br />
<span class="rule"><b>Kumulieren</b> Kann ein Spieler, der von eine +2-Karte betroffen ist, selbst mit einer solchen Karte aufwarten, so i8st dies erlaubt und der nächste Spieler nach ihm muss nun 4 Karten ziehen. Hat dieser ebenfalls eine +2-Karte, so ist auch dies zulässig. Gleiches gilt entsprechend für die +4-Karten. Die Karten dürfen allerdings nicht kombiniert werden. </span><br />
<span class="rule"><b>Jump-In</b> Wenn ein Spieler exakt die gleiche Karte auf der Hand hat, welche soeben ausgespielt wurde (gleiche Farbe und Zahl), so kann er diese sofort ablegen auch wenn er nicht am Zug ist. Die Runde geht jedoch bei dem Spieler weiter, welcher regulär an der Reihe gewesen wäre. </span><br />
<span class="rule"><b>Pennen</b> Wer seinen Einsatz verpasst, muss eine Strafkarte ziehen. </span><br />
<span class="rule"><b>Seven-O (Sieben-Null)</b> Wird eine Null gespielt, so muss jeder Spieler sein komplettes Blatt an den Spielnachbarn in Uhrzeigerrichtugn weitergeben. Wird dagegen eine Sieben ausgespielt, so darf dieser Spieler sein komplettes Blatt mit einem anderen Spieler seiner Wahl tauschen. </span><br />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" data-dismiss="modal">Alles klar!</button>
</div>
</div>
</div>
</div>
<!--double modal-->
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" id="open_rdouble_modal" data-toggle="modal" data-target="#modal_double" [hidden]="true">
Launch modal
</button>
<div class="modal fade" id="modal_double" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="cards-center">
<button type="button" class="btn uno-card double-card" style="margin-right:0;" (click)="playSingle()" [style.background]="'url('+ selectedCard?.tx +')'" placement="bottom" ngbTooltip="Einzelne Karte legen" data-dismiss="modal">1x</button>
<button type="button" class="btn uno-card double-card" style="margin-right:0;" (click)="playDouble()" [style.background]="'url('+ selectedCard?.tx +')'" placement="bottom" ngbTooltip="Doppelt legen" data-dismiss="modal">2x</button>
</div>
</div>
</div>
</div>

View File

@@ -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 */
}

View File

@@ -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,
}});
}
}

View File

@@ -0,0 +1,22 @@
<div class="row h-100">
<div class="col-sm-12 h-100 d-table">
<div class="card card-block d-table-cell align-middle">
<div class="start-box">
<h1>Wie heißt du ?</h1>
<br />
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon1">@</span>
</div>
<input mdbInput type="text" class="form-control" placeholder="Username" aria-label="Username" aria-describedby="basic-addon1" [(ngModel)]="nick">
</div>
<span class="error" *ngIf="nick.length > 15">Der Benutzername ist zu lange</span>
<span class="error" *ngIf="nick.length < 3">Der Benutzername ist zu kurz</span>
<div class="input-group mb-3 ">
<button block="true" [disabled]="!buttonEnabled || (nick.length > 15) || (nick.length < 3)" mdbBtn type="button" gradient="peach" rounded="true" mdbWavesEffect class="btn" (click)="start()">Abfahrt!</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -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;
}

View File

@@ -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);
*/
}
}

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More