├── src ├── config.json ├── .gitignore ├── public │ ├── assets │ │ ├── wall.png │ │ ├── testBall.png │ │ ├── testPaddles.png │ │ ├── button_2_player.png │ │ ├── button_3_player.png │ │ ├── button_4_player.png │ │ └── timerBackground.png │ ├── index.html │ ├── prototype.html │ ├── test2DEngine.html │ ├── js │ │ ├── Game │ │ │ ├── States │ │ │ │ ├── AbstractState.js │ │ │ │ ├── GameRunning.js │ │ │ │ ├── Load.js │ │ │ │ ├── Initial.js │ │ │ │ ├── AllPlayerConnected.js │ │ │ │ ├── PlayerConnected.js │ │ │ │ ├── GameEnded.js │ │ │ │ └── InitializeNewGame.js │ │ │ ├── Wall.js │ │ │ ├── 2PlayerField.js │ │ │ ├── Ball.js │ │ │ ├── Player.js │ │ │ └── Wallbuilder.js │ │ ├── ControlPeerMain.js │ │ ├── DisplayPeerMain.js │ │ ├── EngineTestBounce.js │ │ ├── PrototypeGame.js │ │ ├── ControlPeerCommunicationHandler.js │ │ ├── testWebRTCClient_RAW.js │ │ ├── Game.js │ │ ├── FingerPositionHandler.js │ │ └── DisplayPeerCommunicationHandler.js │ ├── ControlPeer.html │ ├── testWebRTC.html │ └── DisplayPeer.html ├── own_modules │ ├── README.md │ └── WebRTC-COMM.js ├── package.json └── server.js ├── .gitignore ├── docu ├── protokolls │ ├── Milestones.pdf │ └── Milestones.tex ├── maindocumentation │ ├── MBC-Ping-Pong.pdf │ ├── logo │ │ └── HAW_logo.png │ ├── backend │ │ ├── ModulExports.PNG │ │ ├── Modul_Exports.PNG │ │ ├── Topologie_1v1.PNG │ │ ├── Topologie_4v1.PNG │ │ ├── Topologie_ava.PNG │ │ ├── Modul_Callbacks.PNG │ │ ├── Modul_RTCConfig.PNG │ │ ├── Modul_ClientOptions.PNG │ │ ├── Modul_UserClientHowTo.PNG │ │ ├── Modul_UserServerHowTo.PNG │ │ ├── ConnectionBetweenPeers.PNG │ │ ├── Modul_ClientConstructor.PNG │ │ ├── Modul_ServerContructor.PNG │ │ ├── Tabelle_Performance_Chat.PNG │ │ ├── Diagramm_Performance_144hz.PNG │ │ ├── Diagramm_Performance_Chat.PNG │ │ ├── Tabelle_Performance_144hz.PNG │ │ ├── Tabelle_Performance_Normal.PNG │ │ ├── Diagramm_Performance_Normal.PNG │ │ ├── Modul_UserClientDependencies.PNG │ │ ├── Modul_UserServerDependencies.PNG │ │ ├── Modul_UserClientHowToSendMessage.PNG │ │ └── Modul_UserClientHowToMessageCallback.PNG │ ├── frontend │ │ ├── Ping-Pong-2p.png │ │ ├── Ping-Pong-3p.png │ │ ├── Ping-Pong-4p.png │ │ ├── prototype-0.png │ │ └── Ping-Pong-Breakout.png │ ├── MBC-Ping-Pong_Backend_only.pdf │ ├── architecture │ │ ├── Mookup - Initial.png │ │ ├── highLevelAnsatz1.png │ │ ├── highLevelAnsatz2.png │ │ ├── Mookup - GameEnded.png │ │ ├── Mookup - GameRunning.png │ │ ├── PrototypeClassDiagram.png │ │ ├── PrototypeSequenceDiagram.png │ │ ├── StateMachineRelease1.0.png │ │ ├── prototypSequenceDiagram.png │ │ ├── Mookup - FirstPlayerConnected.png │ │ ├── Mookup - SecondPlayerConnected.png │ │ ├── Release1.0ClassDiagramControlPeer.png │ │ ├── Release1.0ClassDiagramDisplayPeer.png │ │ ├── highLevelAnsatz1.xml │ │ ├── highLevelAnsatz2.xml │ │ └── prototypSequenceDiagram.xml │ ├── MBC-Ping-Pong.bib │ ├── make.sh │ ├── make_Backend_only.sh │ ├── README.md │ ├── JavaScript.tex │ ├── MBC-Ping-Pong_Backend_only.tex │ ├── MBC-Ping-Pong.tex │ ├── hawstyle.sty │ ├── Architektur.tex │ └── Frontend.tex └── .gitignore ├── docker-compose-dev.yml ├── docker-compose.yml ├── makebundles.sh ├── startdocker.sh ├── startOnWindows.sh ├── dockerfile ├── README.md └── license /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 11002 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | js.md 3 | /.idea/ 4 | *.iml 5 | npm-debug.log -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | *_Bundle.js 3 | *_bundle.js 4 | *.idea/* 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /src/public/assets/wall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/src/public/assets/wall.png -------------------------------------------------------------------------------- /docu/protokolls/Milestones.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/protokolls/Milestones.pdf -------------------------------------------------------------------------------- /src/public/assets/testBall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/src/public/assets/testBall.png -------------------------------------------------------------------------------- /docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | 5 | mbc-ping-pong: 6 | volumes: 7 | - ./src:/opt/MBC-Ping-Pong 8 | -------------------------------------------------------------------------------- /src/public/assets/testPaddles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/src/public/assets/testPaddles.png -------------------------------------------------------------------------------- /src/public/assets/button_2_player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/src/public/assets/button_2_player.png -------------------------------------------------------------------------------- /src/public/assets/button_3_player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/src/public/assets/button_3_player.png -------------------------------------------------------------------------------- /src/public/assets/button_4_player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/src/public/assets/button_4_player.png -------------------------------------------------------------------------------- /src/public/assets/timerBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/src/public/assets/timerBackground.png -------------------------------------------------------------------------------- /docu/maindocumentation/MBC-Ping-Pong.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/MBC-Ping-Pong.pdf -------------------------------------------------------------------------------- /docu/maindocumentation/logo/HAW_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/logo/HAW_logo.png -------------------------------------------------------------------------------- /docu/maindocumentation/backend/ModulExports.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/ModulExports.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Modul_Exports.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Modul_Exports.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Topologie_1v1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Topologie_1v1.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Topologie_4v1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Topologie_4v1.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Topologie_ava.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Topologie_ava.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/frontend/Ping-Pong-2p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/frontend/Ping-Pong-2p.png -------------------------------------------------------------------------------- /docu/maindocumentation/frontend/Ping-Pong-3p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/frontend/Ping-Pong-3p.png -------------------------------------------------------------------------------- /docu/maindocumentation/frontend/Ping-Pong-4p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/frontend/Ping-Pong-4p.png -------------------------------------------------------------------------------- /docu/maindocumentation/frontend/prototype-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/frontend/prototype-0.png -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Modul_Callbacks.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Modul_Callbacks.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Modul_RTCConfig.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Modul_RTCConfig.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/MBC-Ping-Pong_Backend_only.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/MBC-Ping-Pong_Backend_only.pdf -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Modul_ClientOptions.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Modul_ClientOptions.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/frontend/Ping-Pong-Breakout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/frontend/Ping-Pong-Breakout.png -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/Mookup - Initial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/architecture/Mookup - Initial.png -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/highLevelAnsatz1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/architecture/highLevelAnsatz1.png -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/highLevelAnsatz2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/architecture/highLevelAnsatz2.png -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Modul_UserClientHowTo.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Modul_UserClientHowTo.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Modul_UserServerHowTo.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Modul_UserServerHowTo.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/Mookup - GameEnded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/architecture/Mookup - GameEnded.png -------------------------------------------------------------------------------- /docu/maindocumentation/backend/ConnectionBetweenPeers.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/ConnectionBetweenPeers.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Modul_ClientConstructor.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Modul_ClientConstructor.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Modul_ServerContructor.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Modul_ServerContructor.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Tabelle_Performance_Chat.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Tabelle_Performance_Chat.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/Mookup - GameRunning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/architecture/Mookup - GameRunning.png -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/PrototypeClassDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/architecture/PrototypeClassDiagram.png -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Diagramm_Performance_144hz.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Diagramm_Performance_144hz.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Diagramm_Performance_Chat.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Diagramm_Performance_Chat.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Tabelle_Performance_144hz.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Tabelle_Performance_144hz.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Tabelle_Performance_Normal.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Tabelle_Performance_Normal.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/PrototypeSequenceDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/architecture/PrototypeSequenceDiagram.png -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/StateMachineRelease1.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/architecture/StateMachineRelease1.0.png -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/prototypSequenceDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/architecture/prototypSequenceDiagram.png -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Diagramm_Performance_Normal.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Diagramm_Performance_Normal.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Modul_UserClientDependencies.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Modul_UserClientDependencies.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Modul_UserServerDependencies.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Modul_UserServerDependencies.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/Mookup - FirstPlayerConnected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/architecture/Mookup - FirstPlayerConnected.png -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Modul_UserClientHowToSendMessage.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Modul_UserClientHowToSendMessage.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/Mookup - SecondPlayerConnected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/architecture/Mookup - SecondPlayerConnected.png -------------------------------------------------------------------------------- /docu/maindocumentation/backend/Modul_UserClientHowToMessageCallback.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/backend/Modul_UserClientHowToMessageCallback.PNG -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/Release1.0ClassDiagramControlPeer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/architecture/Release1.0ClassDiagramControlPeer.png -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/Release1.0ClassDiagramDisplayPeer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/docu/maindocumentation/architecture/Release1.0ClassDiagramDisplayPeer.png -------------------------------------------------------------------------------- /src/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MBC Ping-Pong 5 | 6 | 7 | 8 | 9 | 10 |

Initial Ping-Pong index!

11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | 5 | mbc-ping-pong: 6 | build: 7 | context: ./ 8 | dockerfile: dockerfile 9 | image: torbenhaug/mbc-ping-pong 10 | ports: 11 | - "8080:11002" 12 | -------------------------------------------------------------------------------- /makebundles.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | browserify ./src/public/js/DisplayPeerMain.js -o src/public/js/DisplayPeer_Bundle.js 4 | browserify ./src/public/js/ControlPeerMain.js -o src/public/js/ControlPeer_Bundle.js 5 | browserify ./src/public/js/testWebRTCClient_RAW.js -o src/public/js/testWebRTCClient_Bundle.js 6 | 7 | read -------------------------------------------------------------------------------- /src/public/prototype.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Physic Engine Test 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/public/test2DEngine.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Physic Engine Test 6 | 7 | 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /docu/maindocumentation/MBC-Ping-Pong.bib: -------------------------------------------------------------------------------- 1 | 2 | @Article{sample_bib, 3 | author = {Author One and Author Two}, 4 | title = {A Sample Publication}, 5 | year = {2010} 6 | } 7 | 8 | @Misc{WebsiteSample, 9 | Author = "Max Mustermann", 10 | Title = "\emph{Title}", 11 | Note = "\url{http://full.url} 12 | [Accessed: 01.01.1980]", 13 | year = 1998, 14 | } 15 | -------------------------------------------------------------------------------- /startdocker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /opt 4 | 5 | cd MBC-Ping-Pong 6 | 7 | npm install 8 | browserify public/js/DisplayPeerMain.js -o public/js/DisplayPeer_Bundle.js 9 | browserify public/js/ControlPeerMain.js -o public/js/ControlPeer_Bundle.js 10 | browserify public/js/testWebRTCClient_RAW.js -o public/js/testWebRTCClient_Bundle.js 11 | 12 | exec nodejs server.js 13 | -------------------------------------------------------------------------------- /startOnWindows.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | 5 | cd src 6 | 7 | npm install 8 | $APPDATA/npm/browserify.cmd public/js/DisplayPeerMain.js -o public/js/DisplayPeer_Bundle.js 9 | $APPDATA/npm/browserify.cmd public/js/ControlPeerMain.js -o public/js/ControlPeer_Bundle.js 10 | $APPDATA/npm/browserify.cmd public/js/testWebRTCClient_RAW.js -o public/js/testWebRTCClient_Bundle.js 11 | 12 | exec node server.js 13 | -------------------------------------------------------------------------------- /docu/maindocumentation/make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pdflatex -synctex=1 -interaction=nonstopmode "MBC-Ping-Pong".tex 4 | state=$? 5 | if [ ! $state = "0" ]; then 6 | echo "Failed to compile latex!" 1>&2 7 | exit $state 8 | fi 9 | 10 | pdflatex -synctex=1 -interaction=nonstopmode "MBC-Ping-Pong".tex 11 | state=$? 12 | if [ ! $state = "0" ]; then 13 | echo "Failed to compile latex!" 1>&2 14 | exit $state 15 | fi 16 | -------------------------------------------------------------------------------- /docu/maindocumentation/make_Backend_only.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pdflatex -synctex=1 -interaction=nonstopmode "MBC-Ping-Pong_Backend_only".tex 4 | state=$? 5 | if [ ! $state = "0" ]; then 6 | echo "Failed to compile latex!" 1>&2 7 | exit $state 8 | fi 9 | 10 | pdflatex -synctex=1 -interaction=nonstopmode "MBC-Ping-Pong_Backend_only".tex 11 | state=$? 12 | if [ ! $state = "0" ]; then 13 | echo "Failed to compile latex!" 1>&2 14 | exit $state 15 | fi 16 | -------------------------------------------------------------------------------- /docu/maindocumentation/README.md: -------------------------------------------------------------------------------- 1 | # Installation LaTex unter Ubuntu 16.04 2 | - `sudo -i` 3 | - `apt-get update` 4 | - `apt-get install texlive-full` 5 | - `exit` 6 | 7 | # Kompilieren zu einem pdf 8 | - `./make.sh` 9 | 10 | # Editor 11 | Es kann mit jedem beliebigen Texteditor gearbeitet werden.
12 | Wer jedoch Codecompletion und eine Vorschau wünscht, kann eine spezailisierten LaTex-Editor nutzen. Eine entsprechende Liste ist unter https://wiki.ubuntuusers.de/LaTeX-Editoren/ zu finden. 13 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y nodejs && \ 5 | apt-get install -y npm && \ 6 | apt-get install -y git 7 | 8 | RUN apt-get install -y libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++ 9 | 10 | ADD src/ /opt/MBC-Ping-Pong 11 | ADD startdocker.sh /opt/startdocker.sh 12 | 13 | RUN npm install --global browserify 14 | 15 | RUN ln -s /usr/bin/nodejs /usr/bin/node 16 | RUN chmod +x /opt/startdocker.sh 17 | 18 | EXPOSE 11002 19 | 20 | CMD [ "/opt/startdocker.sh"] 21 | -------------------------------------------------------------------------------- /src/public/js/Game/States/AbstractState.js: -------------------------------------------------------------------------------- 1 | var state = function (game) { 2 | this.addPlayer = function () { 3 | console.log("addPlayer is not allowed in current state: " + this.game.state.current) 4 | }; 5 | this.removePlayer = function (playerId) { 6 | console.log("removePlayer is not allowed in current state: " + this.game.state.current) 7 | }; 8 | this.timeout = function (playerId) { 9 | console.log("timeout is not allowed in current state: " + this.game.state.current) 10 | }; 11 | 12 | }; 13 | 14 | module.exports = state; 15 | -------------------------------------------------------------------------------- /src/public/ControlPeer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | Placeholder ControlPeer! 8 |
9 |
10 | undefined:undefined 11 |
12 | 13 |









14 | 15 | 16 | Canvas element not supported. 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/public/testWebRTC.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 | 15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/public/js/ControlPeerMain.js: -------------------------------------------------------------------------------- 1 | var FingerPositionHandler = require('./FingerPositionHandler.js'); 2 | var ControlPeerCommunicationHandler = require('./ControlPeerCommunicationHandler.js'); 3 | 4 | //@FIXME at the moment its assumed, there is no other argument then gameID. This will, when there is more than 1 argument. 5 | ControlPeerCommunicationHandler.init(window.location.hash.substr(8)); 6 | 7 | var fingerPositionHandler = new FingerPositionHandler( 8 | "touchInput", 9 | 40, 10 | [function(x,y){ 11 | document.getElementById("mousePosition").innerHTML = "x= " + x + "y= " + y; 12 | }, 13 | ControlPeerCommunicationHandler.sendPosition] 14 | ); 15 | -------------------------------------------------------------------------------- /src/own_modules/README.md: -------------------------------------------------------------------------------- 1 | ### Client ### 2 | Config: 3 | mode: 4 | maxpeers: num needs to be set when mode is multi 5 | 6 | A Peer in Single mode is connected a one other Peer. If there are multiple Peers in the room, the Single Peer will be connected to the first Multi Peer, or the first Single Peer. 7 | In this Project a Multi-Peer can be refered to a Display-Peer and a Single-Peer to a Control-Peer. 8 | 9 | Status: 10 | init: initital state 11 | connecting: connection credentials are retrieved and distributed 12 | connected: successfull connected to a Peer 13 | disconnected: connection to all Peers lost -------------------------------------------------------------------------------- /src/public/DisplayPeer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Zurück zur Übersicht 7 | 8 |

9 | MBC-Ping-Pong 10 |

11 | 12 |
13 | 14 |
15 |

Join Game

16 | 17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MBC-Ping-Pong", 3 | "version": "1.1.0", 4 | "description": "Browser-based multipleayer Ping-Pong game.", 5 | "license": "SEE LICENCE IN license (https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong/master/license)", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/Transport-Protocol/MBC-Ping-Pong" 9 | }, 10 | "dependencies": { 11 | "express": "^4.10.2", 12 | "browserify": "^8.0.3", 13 | "bufferutil": "^1.3.0", 14 | "utf-8-validate": "^2.0.0", 15 | "socket.io": "^1.3.5", 16 | "socket.io-client": "^1.3.5", 17 | "socket.io-p2p": "^2.2.0", 18 | "qrious": "^2.1.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MBC-Ping-Pong 2 | Project Ping Pong 3 | 4 | ## Prerequires 5 | - Docker https://www.docker.com/products/overview 6 | - Docker-Compose: https://docs.docker.com/compose/install/ 7 | - Linux: The own user should be a member of the docker group 8 | 9 | ## Build: 10 | - go to the root of the project folder (the folder which contains the docker-compose.yml) 11 | - run `docker-compose build` 12 | 13 | ## Run: 14 | - go to the root of the project folder (the folder which contains the docker-compose.yml) 15 | - run `docker-compose up` 16 | - exit with `CTRL + C` 17 | 18 | ## Develop: 19 | - go to the root of the project folder (the folder which contains the docker-compose.yml) 20 | - run `docker-compose -f docker-compose.yml -f docker-compose-dev.yml up` 21 | - exit with `CTRL + C` 22 | -------------------------------------------------------------------------------- /src/public/js/Game/States/GameRunning.js: -------------------------------------------------------------------------------- 1 | var abstractState = require('./AbstractState.js'); 2 | 3 | var state = function (game) { 4 | var self = this; 5 | self.game = game; 6 | abstractState.call(this, self.game); 7 | 8 | this.init = function () { 9 | console.log("GameRunning"); 10 | }; 11 | 12 | this.preload = function () { 13 | // Don't pause game if the form looses focus 14 | this.game.stage.disableVisibilityChange = true; 15 | }; 16 | 17 | this.create = function () { 18 | self.game.properties.ball.start(); 19 | }; 20 | 21 | this.shutdown = function () { 22 | self.game.properties.ball.stop(); 23 | }; 24 | 25 | this.removePlayer = function () { 26 | self.game.state.start("GameEnded", false, false); 27 | }; 28 | }; 29 | 30 | state.prototype = Object.create(abstractState.prototype); 31 | state.prototype.constructor = state; 32 | 33 | module.exports = state; 34 | -------------------------------------------------------------------------------- /src/public/js/DisplayPeerMain.js: -------------------------------------------------------------------------------- 1 | var commHandler = require('./DisplayPeerCommunicationHandler'); 2 | var Game = require('./Game.js') 3 | 4 | // Example of a exported function call 5 | //commHandler.initPlayer(); 6 | 7 | commHandler.addPlayerToGameListener(Game.addPlayerToGame); 8 | commHandler.removePlayerFromGameListener(Game.removePlayerFromGame); 9 | commHandler.addPositionToPlayerBufferListener(Game.addPositionToPlayerBuffer); 10 | 11 | window.addTestPlayer = function(){ 12 | var player = Game.addPlayerToGame(); 13 | var x = 0; 14 | var y = 0; 15 | 16 | /* 17 | if(player !== undefined){ 18 | var interval = setInterval( 19 | function(){ 20 | Game.addPositionToPlayerBuffer(player, x++, y++); 21 | }, 22 | 500 23 | ); 24 | } 25 | */ 26 | this.removeTestPlayer = function(){ 27 | //clearInterval(interval); 28 | Game.removePlayerFromGame(player); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/public/js/Game/States/Load.js: -------------------------------------------------------------------------------- 1 | var abstractState = require('./AbstractState.js'); 2 | 3 | var state = function (game) { 4 | abstractState.call(this, game); 5 | 6 | this.init = function () { 7 | console.log("load"); 8 | }; 9 | 10 | this.preload = function () { 11 | this.game.load.spritesheet('paddle', 'assets/testPaddles.png', 9, 100, 6); 12 | this.game.load.image('wall', 'assets/wall.png'); 13 | this.game.load.image('ball', 'assets/testBall.png'); 14 | this.game.load.image('timerBackground', 'assets/timerBackground.png'); 15 | }; 16 | 17 | this.create = function () { 18 | this.initPhysics(); 19 | this.game.state.start("InitializeNewGame"); 20 | }; 21 | 22 | this.initPhysics = function () { 23 | this.game.physics.startSystem(Phaser.Physics.P2JS); 24 | this.game.physics.p2.restitution = 1.0017; 25 | this.game.physics.p2.applyDamping = false; 26 | this.game.physics.p2.setImpactEvents(true); 27 | }; 28 | }; 29 | 30 | state.prototype = Object.create(abstractState.prototype); 31 | state.prototype.constructor = state; 32 | 33 | module.exports = state; 34 | -------------------------------------------------------------------------------- /src/public/js/Game/Wall.js: -------------------------------------------------------------------------------- 1 | var Wall = function (game, sprite, from, to) { 2 | Phaser.Sprite.call( 3 | this, 4 | game, 5 | from.x + Math.sign(to.x - from.x) * (Phaser.Math.difference(from.x, to.x) / 2), 6 | from.y + Math.sign(to.y - from.y) * (Phaser.Math.difference(from.y, to.y) / 2), 7 | sprite 8 | ); 9 | 10 | this.anchor.setTo(0, 0); 11 | this.height = Phaser.Math.distance(from.x, from.y, to.x, to.y); 12 | 13 | game.physics.p2.enable(this, this.game.properties.debug); 14 | 15 | this.body.rotation = Phaser.Math.angleBetween(from.x, from.y, to.x, to.y) + 0.5 * Math.PI; 16 | this.body.kinematic = true; 17 | 18 | // Vergrößere die BoundingBox um tunneling zu minimieren 19 | this.body.setRectangle(64, this.height, -32, 0); 20 | 21 | this.move=function(from,to) { 22 | this.body.x = from.x + Math.sign(to.x - from.x) * (Phaser.Math.difference(from.x, to.x) / 2); 23 | this.body.y = from.y + Math.sign(to.y - from.y) * (Phaser.Math.difference(from.y, to.y) / 2); 24 | } 25 | }; 26 | 27 | Wall.prototype = Object.create(Phaser.Sprite.prototype); 28 | Wall.prototype.constructor = Wall; 29 | 30 | //Interface 31 | module.exports = Wall; 32 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Just another repo for transport protocol development 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/public/js/EngineTestBounce.js: -------------------------------------------------------------------------------- 1 | var gameProperties = { 2 | name: 'engineTest', 3 | width: 320, 4 | height: 320, 5 | }; 6 | 7 | var mainState = function(game){}; 8 | mainState.prototype = { 9 | preload: function () { 10 | game.load.image('ball','assets/testBall.png') 11 | this.ball 12 | }, 13 | 14 | create: function () { 15 | // Add Ball 16 | this.ball = game.add.sprite(game.world.centerX, game.world.centerY, 'ball'); 17 | this.ball.anchor.set(0.5, 0.5); 18 | 19 | game.physics.startSystem(Phaser.Physics.ARCADE); 20 | game.physics.enable(this.ball, Phaser.Physics.ARCADE); 21 | 22 | // Ball config: 23 | this.ball.checkWorldBounds = true; 24 | this.ball.body.collideWorldBounds = true; 25 | this.ball.body.immovable = true; //An immovable Body will not receive any impacts from other bodies. 26 | this.ball.body.bounce.set(1); 27 | this.ball.body.velocity.setTo(200,0); 28 | }, 29 | 30 | update: function () { 31 | 32 | }, 33 | }; 34 | 35 | var game = new Phaser.Game(gameProperties.width, gameProperties.height, Phaser.CANVAS, gameProperties.name); 36 | game.state.add('main', mainState); 37 | game.state.start('main'); -------------------------------------------------------------------------------- /src/public/js/PrototypeGame.js: -------------------------------------------------------------------------------- 1 | var gameProperties = { 2 | name: 'engineTest', 3 | width: 640, 4 | height: 320, 5 | playerCount: 0, 6 | aryPlayers: [] 7 | }; 8 | 9 | 10 | 11 | var mainState = function(game) {}; 12 | mainState.prototype = { 13 | preload: function() { 14 | // New spritesheet, width:9, height:100, count:6 (frames) 15 | game.load.spritesheet('paddle', 'assets/testPaddles.png', 9, 100, 6); 16 | }, 17 | 18 | create: function() {}, 19 | 20 | update: function() {}, 21 | }; 22 | 23 | var game = new Phaser.Game(gameProperties.width, gameProperties.height, Phaser.CANVAS, gameProperties.name); 24 | game.state.add('main', mainState); 25 | game.state.start('main'); 26 | 27 | function addNewPlayer() { 28 | var newId = gameProperties.playerCount++; 29 | if (newId > 5) { 30 | console.log("ERR Player limit exceed") 31 | return; 32 | } 33 | var player = { 34 | id: newId, 35 | x: 80 * newId, 36 | y: game.world.centerY, 37 | sprite: undefined 38 | }; 39 | console.log(player); 40 | gameProperties.aryPlayers.push(player); 41 | assignPlayerSprite(player); 42 | } 43 | 44 | function assignPlayerSprite(player) { 45 | player.sprite = game.add.sprite(player.x, player.y, 'paddle'); 46 | player.sprite.frame = player.id; 47 | } 48 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | , app = express() 3 | , server = require('http').createServer(app) 4 | , config = require('./config.json') 5 | , io = require('socket.io')(server) 6 | , sigserver = require('./own_modules/WebRTC-COMM.js').Server 7 | , sigserverrooms = require('./own_modules/WebRTC-COMM.js').ServerRooms; 8 | 9 | // Give each new User a better readable id for logging 10 | var nextID = 1; 11 | var allUsers = []; 12 | 13 | // List of all game-rooms, most likely one per monitor 14 | // @TODO need generation of rooms by monitor 15 | var rooms = [{ 16 | users: [], 17 | userCount: 0, 18 | state: 'idle', 19 | name: 'testroom' 20 | }]; 21 | 22 | server.listen(config.port); 23 | 24 | app.use('/' ,express.static('public/')); 25 | app.use('/node_modules', express.static('node_modules/')); 26 | 27 | console.log(rooms); 28 | console.log(sigserverrooms); 29 | 30 | app.get('/', function(req, res) { 31 | res.sendfile('public/index.html'); 32 | }); 33 | 34 | // Connection is called upon client connection 35 | io.on('connection', function(socket){ 36 | 37 | new sigserver(socket, {}); 38 | 39 | 40 | // This function is only needed as backup if 41 | // no ICE candidates can be found :( 42 | socket.on('changeposition', function(data){ 43 | console.log("Got from user."); 44 | room.users.forEach(function(user){ 45 | user.emit('changeposition', data); 46 | }); 47 | }); 48 | 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/highLevelAnsatz1.xml: -------------------------------------------------------------------------------- 1 | 7Vpbc5s4FP41fswO4s5j46Tdnelud9ad2fZRBhnUYMQIEdv76/cIhEFAbCfxJU39ZPTpgC7fd46OJE+s6XL9ieM8+ZNFJJ2YRrSeWHcT00QIGfAjkU2NOMivgZjTSBm1wIz+RxSo3otLGpFCMxSMpYLmOhiyLCOh0DDMOVvpZguW6q3mOCYDYBbidIj+SyOR1Khvui3+O6Fx0rSM3KCuWeLGWI2kSHDEVh3Iup9YU86YqJ+W6ylJ5eQ181K/9/GJ2m3HOMnEIS94qhti04yNRDBUVWRcJCxmGU7vW/SWszKLiPyAAaVELFN4RPBI1lR8k/Bvjip9b2oywTedKln8rj5AsuiD5ASKYYqLgoY1+JGmzYd/ECE2Sga4FAygtmufGcuVXT0YOYInp0NBBSt5qKxMpSDMY6KsrC0VoGHClgT6CyacpFjQR/3rWIkp3tqpV2FUeNMxyBnNRNH58t8SAAPlF6anRKG8Atk97vbZG5o9PNQ9aEqdobRQpYdxbfhvURtfE5pdXh/2z6gP0z+uPhA6hUBQRx4dRXzbKqKRh6rbCiRjGdGUYTxDGQWwK4ZhqII7QjuagJBxCQU5lq0pwnJ3K2iP/esVZF4V9FIFBW9BQOYeAVmmscv+1QJS8/KI01KNeEb4I+EDWQEmKCRwH1Iaw/pxJyRvt1iVUrIQkqschzSLv1ac+i3wuaq+M1vkH5XcoQpLcC6bCcu5lE9RawVJ8USUQ/5JmWwE+JTJ3+2CZaJRuK0rOGRLKZqOUGS/yXq3VIYiUC/Yts6Wiv/Gqs1ZUYMl3Xy1YW1MNx3udlLjXN61D3XdfSEgwkVS9erlDmwdP4c4lAhr4CNfSpGXYppSUvnqcBn/Nb1FD1X2OZ3F3sfRX1eORjjabqPPQhKyTxHS9A3RNoqNbIgGWYYKVt0Uo7dpelVGc4SwF5wg8z2UrmDgU1PQKWfpNfDpTmVauldZjnlOrzKGOZwgJeHX2Lc7mzsvTS7GiwCjwA+8OZpH3g1yB7xNTDcV1Tw9wmMsH2cCnLoIEwKWf2QJTgVprKDFrmFOoUEW04cB2zBlQp/ZAtz4gUxZyngb5BYQ4XpQo4gQphg2BdZtXzhLGkVVmF4lVJAZyEG2ueI4PwF/XqDxh8YWLzTCn3MS+ryh20kKcFnEGJT/TknwHI2DpngZCvwrBbD6OBflYJgl1KtPmcUFgbXhHRNhujoTlu9dkAnzqTzgF2CivzRcmAk0YOLL/Ad5EDkrqEx/SDay0jfQnPeRl6QDT6Hvk3/L6N3sHJoa2Cfhf+RUtfLEAmZG3oG/RRY4k9KqUvObwDiSWwa944ZDl6qjHAnNw7kD5LjewkKRiW9Oc/ygn6h6u45Uq8pnHkC8mRv9U1+4abQ+7xJjyPQZzs53Mm3+zExf5GLM9nsruP5fjOHFWD/3OvJ/N4aaOmi7fl2yD16yXZ3w0cu1I60NUGz/EVaLof1fnXX/Pw== -------------------------------------------------------------------------------- /src/public/js/ControlPeerCommunicationHandler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Handler for Communication from and to the Control Peer. 3 | * The Control Peer represents the Player. 4 | * 5 | * For further information: 6 | * 7 | * Sequence including this Entity: 8 | * https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong 9 | * /master/docu/maindocumentation/architecture/prototypSequenceDiagram.png 10 | * 11 | * Section in documentation: 12 | * Chapter: Architecture 13 | * Section: Prototyp 14 | */ 15 | 16 | var io = require('socket.io-client'); 17 | var sigClient = require('./../../own_modules/WebRTC-COMM.js').Client; 18 | 19 | //Interface 20 | module.exports.init = init; 21 | module.exports.sendPosition = sendPosition; 22 | 23 | var opts = { "mode": "single" }; 24 | 25 | // Predefined vars used in init 26 | var iosocket; 27 | var client; 28 | 29 | // Statecheck replacers 30 | // @TODO replace with states 31 | var isPeer2Peer = false; 32 | var isConnected = false; 33 | 34 | // @TODO check for name collision 'init' 35 | function init(gameid){ 36 | initCommunication(gameid); 37 | } 38 | 39 | function initCommunication(gameid){ 40 | 41 | iosocket = io.connect(); 42 | 43 | iosocket.on('connect', function(){ 44 | console.log("Now Connected to the Server."); 45 | 46 | if( !isConnected ) { 47 | client = new sigClient(iosocket, opts, gameid, null); 48 | } 49 | 50 | isConnected = true; 51 | }); 52 | } 53 | 54 | 55 | 56 | function sendPosition(posx, posy){ 57 | console.log("Sending new Position: ["+posx+","+posy+"]"); 58 | 59 | var position = { posX: posx, posY: posy }; 60 | 61 | client.sendMessage("changeposition", JSON.stringify(position)); 62 | } 63 | -------------------------------------------------------------------------------- /src/public/js/testWebRTCClient_RAW.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file needs to be "Browserified". 3 | * Install: "npm install --global browserify" 4 | * Run: "browserify [PATH]/testWebRTCCLient_RAW.js -o [PATH]/testWebRTCClient_Bundle.js" 5 | */ 6 | 7 | var io = require('socket.io-client'); 8 | var sigclient = require('./../../own_modules/WebRTC-COMM.js').Client; 9 | var iosocket = io.connect(); 10 | 11 | var connected = false; 12 | var connobj; 13 | 14 | var btn_connect_single = document.getElementById('connect_single'); 15 | var btn_connect_multi = document.getElementById('connect_multi'); 16 | var btn_sendping = document.getElementById('sendping'); 17 | var btn_toggletec = document.getElementById('toggletec'); 18 | var fld_room = document.getElementById('roomfield'); 19 | 20 | // Called automatically when connection is established 21 | iosocket.on('connect', function(){ 22 | console.log("Socket is connected."); 23 | }); 24 | 25 | 26 | 27 | // Add Event Listeners for buttons 28 | btn_connect_single.addEventListener('click', connecttomonitor_single); 29 | btn_connect_multi.addEventListener('click', connecttomonitor_multi); 30 | btn_sendping.addEventListener('click', sendping); 31 | btn_toggletec.addEventListener('click', toggletec); 32 | 33 | // Connect to a room on the Server 34 | // Later used to upgrade all users of that room to WebRTC 35 | // Monitor should create room and player should join 36 | function connecttomonitor_single(){ 37 | console.log("Connect pressed."); 38 | 39 | connobj = new sigclient(iosocket, {}, "testroom", null); 40 | } 41 | 42 | function connecttomonitor_multi(){ 43 | console.log("Connect pressed."); 44 | 45 | connobj = new sigclient(iosocket, {"mode":"multi", "maxpeers":999}, "testroom", null); 46 | } 47 | 48 | // Simple ping 49 | function sendping(){ 50 | console.log("Send Ping pressed."); 51 | connobj.sendMessage("nothing", "hallo"); 52 | } 53 | -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/highLevelAnsatz2.xml: -------------------------------------------------------------------------------- 1 | 7VvbcqM4EP0aHmcKCTD2Y+xkLlUzu1vrqd3ZRxlk0A5GLiFiZ79+JRAGcUmcBIzj+Mmi1UKiz+mW1JINa7HZf2ZoG36nPo4MaPp7w7o1IARgZoofKXnIJa5p54KAEV8plYIl+Q8roWoXpMTHiabIKY042epCj8Yx9rgmQ4zRna62ppHe6xYFuCFYeihqSv8mPg9z6RROSvkXTIKw6BlMZnnNBhXK6kuSEPl0VxFZd4a1YJTyvLTZL3AkjVfYJW/3qaP2MDCGY35MgwlC6xkCs+nMXYGV736AaqT3KErV1y4xu8dMjZc/FEYQMk6ETW4iEsRCxOnWsOZIPUV4LQYwT7bII3HwQ9bdTkvBt6z6FpaSP5W9QCYL0VZ246UrLB9zBgj4rblPmICUUNlJQlNpz/maxnypRmaL55BvIqkvih7dEE+V1ZeJceN9p73AAQVBX0w3mLMHoaIa2LZiqmLuVOG4K2kACllYpUABOFLUCw6vLtERBQVQO1g+nHhr2zZN5JjuyvxgNzDBvuCpeqSMhzSgMYruSumc0TT2sXyhqRsK7wn/WSn/I1U+uo58jMVAf6om2UNeKev+xZw/KA9FKadCVHb8jWa4l7aX43vc8uJzaMo83EFPq3B3xALM280C2xFkOEKc3Ov9t8GRNb1hDD1UFLaUxDypvPkPKSiJIQxV5YUDan5XUz/woV1fFPIBlLw4fMlRVOk0XMWvf0/5NuWLiOBsZKDBpnfp4ZYOzEkdvImaNYSL5557cHLNwz86VR+XrRLha/xGzppCENMYF7JPRH6Iau/XNISkUn9slPBREmYj7zNk2M2Q0VRyHieFNdVYYQPYfzh3hg/n5xbNW6DpO5q/3PfsRsRciCjEaHQNmbp3AEf3DsuBI66KJqdxo1O7SkeAet6q5ul1iFNDJHdN1aoGymuXJM6TDvbb1cEyh3LhiA7WBG7SAM6Ak4hndroXxUAWl1wQM/FCLDS/xiGKOC60RI8Vxba2OEmEsb+jWGylNxkX2lvW2CFMzHUkEsGnX3hBI8rKxclarExqooJBnuhMbHateZ1oG+L7WazYhYTjpaCP7HPH0HaAXaY70/A+wFjFG7TgXffefuB2G3Avt0TEnTQJ0Aq3wSerIxqQX0XlitXRfh9ITvRtIQDOmEC25HY4TjFL4yDBIhpmaF4mEGBm6SF06o6IhG2+XyRs0z0nJJp5kZaA1sf09j4C3iFh8typa5CVig07/CwRlpHp/HNEgVFJrWypafYEiqmDAscFxbq6XL8ZS31qg7NR0R0kjaWnLIHRnbI0jWenLL0Iie2GZ1SzluD0W/zi6O3xRGUHF4bPh9nTQYA1uo+bgNFIUL5NZI9LQb8W2RedW8F68LCfd3BV0+//4MqeXWn3qpzhWdLOBu55085p5vZPf7j+Nml33DzWSz772bSb6ok164ljensCH9MfgHZwZNrBS+ed/RZ4B+vZqsF5N9BFA/eRmwbua68anAnv2u4kdZBzjHW70zzHPm67/RdmKxL7MiWJ0vUKpdfNtsxv6Z4JnDE3284wm21turjQtfExW7IRnXaQWww6sPBCkT1m1zMiss3T1Gs4fnk4ntX2hYXjnCIct15V05C92wsTxZhdr9AbXRdsnbYJ9GSXxZpHEUuexgYULzJ/pEyVvnqHg4bqRYbLdKnalQXn2NOEPo5n6/BcY2WPwNZWrvaAx0TisfwPU74/Lf8JZt39Dw== -------------------------------------------------------------------------------- /docu/protokolls/Milestones.tex: -------------------------------------------------------------------------------- 1 | \documentclass[12pt,a4paper]{article} 2 | \usepackage[utf8]{inputenc} 3 | \usepackage[german]{babel} 4 | \usepackage[T1]{fontenc} 5 | \usepackage{amsmath} 6 | \usepackage{amsfonts} 7 | \usepackage{amssymb} 8 | \usepackage{graphicx} 9 | \author{Andreas Mueller} 10 | \title{Milestones MBC-Ping-Pong} 11 | \begin{document} 12 | 13 | \maketitle 14 | \newpage 15 | \tableofcontents 16 | \newpage 17 | 18 | \section{Meilensteine} 19 | \subsection{25.11.2016 Projekt Aufsetzen} 20 | Komponenten wie Node.JS github, npm sowie simple Standartanwendung auf Node.js. 21 | 22 | \subsection{16.12.2016 Prototyp (Technik)} 23 | Kommunikationsaufbau WebRTC: Smartphone -> Monitor, node.JS Server als Mittelsmann. (ICE-Server?!) 24 | 25 | Positionsdaten vom Smartphone abgreifen. 26 | Positionsdaten von Frontend (Smartphone) an Backend (Monitor) senden. 27 | Frontend (Monitor) zeigt Kugel in Canvas (eventuell inkl. WebGL) gemäß empfangenen positionsdaten (x, y). 28 | Es können sich 2 Personen (Spieler) mit der selben Spielsession verbinden. 29 | 30 | REST für Spielaufruf, webRTC für andauernde Kommunikation. 31 | Node.JS Server für static content, 32 | sowie Kommunikationsaufbau für WebRTC. 33 | 34 | github.com/nplab/WebRTC-Data-Channel-Playground 35 | 36 | \subsection{06.01.2017 Zwei Spieler} 37 | Anzeige der Spielobjekte auf Client-Seite. 38 | Übertragung relevanter Positionsdaten. 39 | Spieler soll spielen können. 40 | Daher: Spielbar! 41 | 42 | \subsection{06.01.2017 Angestrebtes Release 1.0} 43 | Release der Version 1.0. 44 | Senden an Martin. 45 | Spielen in Live-Umgebung. 46 | 47 | \subsection{bis 24.02.2017 Diverse Features TBD} 48 | Austrittswinkel abhängig von trefferposition auf Balken abhängig. 49 | Drall auf Ball bei bewegtem Balken bei Treffer. 50 | 51 | \subsection{24.02.2017 Definitives Release 1.x} 52 | Release der Version 1.x. 53 | Inklusive aller bis hier implementiereten Zusatz-Features. 54 | 55 | 56 | 57 | 58 | \end{document} 59 | -------------------------------------------------------------------------------- /src/public/js/Game/States/Initial.js: -------------------------------------------------------------------------------- 1 | var abstractState = require('./AbstractState.js'); 2 | var Ball = require('./../Ball.js'); 3 | var Player = require('./../Player.js'); 4 | var QRious = require('qrious'); 5 | var wallBuilder = require("./../Wallbuilder.js"); 6 | 7 | 8 | var state = function (game) { 9 | var self = this; 10 | self.game = game; 11 | abstractState.call(this, self.game); 12 | 13 | 14 | this.init = function (playercount) { 15 | self.wallBuilder = wallBuilder; 16 | console.log("Initial"); 17 | self.playerCount = playercount; 18 | }; 19 | 20 | this.preload = function () { 21 | // Don't pause game if the form looses focus 22 | this.game.stage.disableVisibilityChange = true; 23 | 24 | self.game.properties.ball = new Ball(self.game, self.game.world.width / 2, self.game.world.height / 2); 25 | self.game.add.existing(self.game.properties.ball); 26 | this.buildField(); 27 | var url = window.location.href.slice(0, -1 * "DisplayPeer.html".length) + "ControlPeer.html#gameId=" + self.game.properties.gameId; 28 | document.getElementById('joinGamePanel').style.visibility = 'visible'; 29 | var qr = new QRious({ 30 | element: document.getElementById('qr-code'), 31 | value: url 32 | }); 33 | document.getElementById('joinGameUrl').innerHTML = "" + url + "" 34 | }; 35 | 36 | this.addPlayer = function () { 37 | var player = new Player(self.game, self.wallBuilder.getPlayerInfoPack(0), self.game.properties.ball, 'paddle', 0); 38 | self.game.state.start("PlayerConnected", false, false, self.playerCount); 39 | return player; 40 | }; 41 | this.buildField = function () { 42 | switch (self.playerCount) { 43 | case 2: 44 | wallBuilder.build2PlayerField(game, "wall"); 45 | break; 46 | case 3: 47 | wallBuilder.build3PlayerField(game, "wall"); 48 | break; 49 | case 4: 50 | wallBuilder.build4PlayerField(game, "wall"); 51 | break; 52 | } 53 | } 54 | }; 55 | 56 | state.prototype = Object.create(abstractState.prototype); 57 | state.prototype.constructor = state; 58 | 59 | module.exports = state; 60 | -------------------------------------------------------------------------------- /src/public/js/Game/States/AllPlayerConnected.js: -------------------------------------------------------------------------------- 1 | var abstractState = require('./AbstractState.js'); 2 | var Ball = require('./../Ball.js'); 3 | var Player = require('./../Player.js'); 4 | 5 | const TIME_UNTIL_GAME_STARTS_IN_SECONDS = 10; 6 | 7 | var state = function (game) { 8 | var self = this; 9 | self.game = game; 10 | abstractState.call(this, self.game); 11 | 12 | this.init = function () { 13 | console.log("SecondPlayerConnected"); 14 | }; 15 | 16 | this.preload = function () { 17 | // Don't pause game if the form looses focus 18 | this.game.stage.disableVisibilityChange = true; 19 | 20 | document.getElementById('joinGamePanel').style.visibility = 'hidden'; 21 | 22 | //ToDo: Convert to Object, use Anchor 23 | self.circle = game.add.sprite(game.width / 2 - 25, game.height / 2 - 100, 'timerBackground'); 24 | 25 | var timerTextStyle = {font: "bold 32px Arial", fill: "#FF0000", boundsAlignH: "center", boundsAlignV: "middle"}; 26 | self.timerText = game.add.text(0, 0, "" + TIME_UNTIL_GAME_STARTS_IN_SECONDS, timerTextStyle); 27 | //ToDo: Convert to Object, use Anchor 28 | self.timerText.setTextBounds(self.circle.x, self.circle.y, self.circle.width, self.circle.height); 29 | }; 30 | 31 | this.create = function () { 32 | self.timer = game.time.create(); 33 | self.timerEvent = self.timer.add(Phaser.Timer.SECOND * TIME_UNTIL_GAME_STARTS_IN_SECONDS, function () { 34 | this.game.state.states[this.game.state.current].timeout(); 35 | }, this); 36 | self.timer.start(); 37 | }; 38 | 39 | this.timeout = function () { 40 | this.game.state.start("GameRunning", false, false); 41 | }; 42 | 43 | this.render = function () { 44 | self.timerText.text = Math.round(self.timer.duration / 1000); 45 | }; 46 | 47 | this.removePlayer = function (playerId) { 48 | this.game.state.start("InitializeNewGame", true, false); 49 | }; 50 | 51 | this.shutdown = function () { 52 | self.circle.destroy(); 53 | self.timerText.destroy(); 54 | self.timer.stop(); 55 | self.timer.destroy(); 56 | } 57 | }; 58 | 59 | state.prototype = Object.create(abstractState.prototype); 60 | state.prototype.constructor = state; 61 | 62 | module.exports = state; 63 | -------------------------------------------------------------------------------- /src/public/js/Game/2PlayerField.js: -------------------------------------------------------------------------------- 1 | var Wall = require('./Wall.js'); 2 | 3 | 4 | var fieldNodes = { 5 | upperWall: {from: {x: 0, y: 64}, to: {x: 640, y: 64}}, 6 | rightWall: {from: {x: 640, y: 64}, to: {x: 640, y: 440}}, 7 | lowerWall: {from: {x: 640, y: 440}, to: {x: 0, y: 440}}, 8 | leftWall: {from: {x: 0, y: 440}, to: {x: 0, y: 64}} 9 | }; 10 | 11 | var playerInfoPack = []; 12 | 13 | var buildField = function (game, wallSprite) { 14 | //create Score fpr Player 0 15 | var style0 = {font: "bold 32px Arial", fill: "#fff", boundsAlignH: "right", boundsAlignV: "middle"}; 16 | var scorePlayer0 = game.add.text(0, 0, "0", style0); 17 | scorePlayer0.setTextBounds(0, 0, (game.width / 2) - 20, 64); 18 | 19 | //Create separator 20 | var styleSeparator = {font: "bold 32px Arial", fill: "#fff", boundsAlignH: "center", boundsAlignV: "middle"}; 21 | var separator = game.add.text(0, 0, ":", styleSeparator); 22 | separator.setTextBounds((game.width / 2) - 20, 0, 40, 64); 23 | 24 | var style1 = {font: "bold 32px Arial", fill: "#fff", boundsAlignH: "left", boundsAlignV: "middle"}; 25 | var scorePlayer1 = game.add.text(0, 0, "0", style1); 26 | scorePlayer1.setTextBounds((game.width / 2) + 20, 0, (game.width / 2) - 20, 64); 27 | var walls = game.add.group(); 28 | 29 | // Sichtbare Wände 30 | var upperWall = createWall(game, wallSprite, fieldNodes.upperWall); 31 | var lowerWall = createWall(game, wallSprite, fieldNodes.lowerWall); 32 | var rightWall = createWall(game, wallSprite, fieldNodes.rightWall); 33 | var leftWall = createWall(game, wallSprite, fieldNodes.leftWall); 34 | walls.add(upperWall); 35 | walls.add(lowerWall); 36 | walls.add(rightWall); 37 | walls.add(leftWall); 38 | 39 | playerInfoPack = [ 40 | {"upperWall": upperWall, "lowerWall": lowerWall, "pointWall": leftWall, "opponentScoreText": scorePlayer1}, 41 | {"upperWall": upperWall, "lowerWall": lowerWall, "pointWall": rightWall, "opponentScoreText": scorePlayer0} 42 | ]; 43 | 44 | return walls; 45 | }; 46 | 47 | function getPlayerInfoPack(playerNumber) { 48 | return playerInfoPack[playerNumber]; 49 | } 50 | 51 | function createWall(game, sprite, positions) { 52 | return new Wall(game, sprite, positions.from, positions.to); 53 | } 54 | 55 | module.exports.buildField = buildField; 56 | module.exports.getPlayerInfoPack = getPlayerInfoPack; 57 | -------------------------------------------------------------------------------- /src/public/js/Game/States/PlayerConnected.js: -------------------------------------------------------------------------------- 1 | var abstractState = require('./AbstractState.js'); 2 | var Ball = require('./../Ball.js'); 3 | var Player = require('./../Player.js'); 4 | var wallBuilder = require("./../Wallbuilder.js"); 5 | 6 | const TIME_TO_CONNECT_SECOND_PLAYER_IN_SECONDS = 30; 7 | 8 | var state = function (game) { 9 | var self = this; 10 | self.game = game; 11 | var playercount = 1; 12 | abstractState.call(this, self.game); 13 | 14 | this.init = function (maxPlayers) { 15 | playercount = 1; 16 | self.maxplayers = maxPlayers; 17 | self.wallBuilder = wallBuilder; 18 | console.log("FirstPlayerConnected"); 19 | }; 20 | 21 | this.preload = function () { 22 | // Don't pause game if the form looses focus 23 | this.game.stage.disableVisibilityChange = true; 24 | 25 | self.circle = game.add.sprite(game.width / 2 - 25, game.height / 2 - 100, 'timerBackground'); 26 | 27 | var timerTextStyle = {font: "bold 32px Arial", fill: "#FF0000", boundsAlignH: "center", boundsAlignV: "middle"}; 28 | self.timerText = game.add.text(0, 0, "" + TIME_TO_CONNECT_SECOND_PLAYER_IN_SECONDS, timerTextStyle); 29 | self.timerText.setTextBounds(self.circle.x, self.circle.y, self.circle.width, self.circle.height); 30 | }; 31 | 32 | this.create = function () { 33 | self.timer = game.time.create(); 34 | self.timerEvent = self.timer.add(Phaser.Timer.SECOND * TIME_TO_CONNECT_SECOND_PLAYER_IN_SECONDS, function () { 35 | this.game.state.states[this.game.state.current].timeout(); 36 | }, this); 37 | self.timer.start(); 38 | }; 39 | 40 | this.addPlayer = function () { 41 | var player = new Player(self.game, self.wallBuilder.getPlayerInfoPack(playercount), self.game.properties.ball, 'paddle', playercount); 42 | console.log("NAME: " + player.getPlayerName()); 43 | playercount++; 44 | console.log(playercount); 45 | console.log(self.maxplayers) 46 | if (playercount == self.maxplayers) { 47 | this.game.state.start("AllPlayersConnected", false, false); 48 | } 49 | return player; 50 | }; 51 | 52 | this.timeout = function () { 53 | this.game.state.start("InitializeNewGame", true, false); 54 | }; 55 | 56 | this.render = function () { 57 | self.timerText.text = Math.round(self.timer.duration / 1000); 58 | }; 59 | 60 | this.removePlayer = function (playerId) { 61 | this.game.state.start("InitializeNewGame", true, false); 62 | }; 63 | 64 | this.shutdown = function () { 65 | self.circle.destroy(); 66 | self.timerText.destroy(); 67 | self.timer.stop(); 68 | self.timer.destroy(); 69 | } 70 | }; 71 | 72 | state.prototype = Object.create(abstractState.prototype); 73 | state.prototype.constructor = state; 74 | 75 | module.exports = state; 76 | -------------------------------------------------------------------------------- /docu/.gitignore: -------------------------------------------------------------------------------- 1 | ## Core latex/pdflatex auxiliary files: 2 | *.aux 3 | *.lof 4 | *.log 5 | *.lot 6 | *.fls 7 | *.out 8 | *.toc 9 | *.fmt 10 | *.fot 11 | *.cb 12 | *.cb2 13 | 14 | ## Intermediate documents: 15 | *.dvi 16 | *-converted-to.* 17 | # these rules might exclude image files for figures etc. 18 | # *.ps 19 | # *.eps 20 | # *.pdf 21 | 22 | ## Bibliography auxiliary files (bibtex/biblatex/biber): 23 | *.bbl 24 | *.bcf 25 | *.blg 26 | *-blx.aux 27 | *-blx.bib 28 | *.brf 29 | *.run.xml 30 | 31 | ## Build tool auxiliary files: 32 | *.fdb_latexmk 33 | *.synctex 34 | *.synctex.gz 35 | *.synctex.gz(busy) 36 | *.pdfsync 37 | 38 | ## Auxiliary and intermediate files from other packages: 39 | # algorithms 40 | *.alg 41 | *.loa 42 | 43 | # achemso 44 | acs-*.bib 45 | 46 | # amsthm 47 | *.thm 48 | 49 | # beamer 50 | *.nav 51 | *.snm 52 | *.vrb 53 | 54 | # cprotect 55 | *.cpt 56 | 57 | # fixme 58 | *.lox 59 | 60 | #(r)(e)ledmac/(r)(e)ledpar 61 | *.end 62 | *.?end 63 | *.[1-9] 64 | *.[1-9][0-9] 65 | *.[1-9][0-9][0-9] 66 | *.[1-9]R 67 | *.[1-9][0-9]R 68 | *.[1-9][0-9][0-9]R 69 | *.eledsec[1-9] 70 | *.eledsec[1-9]R 71 | *.eledsec[1-9][0-9] 72 | *.eledsec[1-9][0-9]R 73 | *.eledsec[1-9][0-9][0-9] 74 | *.eledsec[1-9][0-9][0-9]R 75 | 76 | # glossaries 77 | *.acn 78 | *.acr 79 | *.glg 80 | *.glo 81 | *.gls 82 | *.glsdefs 83 | 84 | # gnuplottex 85 | *-gnuplottex-* 86 | 87 | # hyperref 88 | *.brf 89 | 90 | # knitr 91 | *-concordance.tex 92 | # TODO Comment the next line if you want to keep your tikz graphics files 93 | *.tikz 94 | *-tikzDictionary 95 | 96 | # listings 97 | *.lol 98 | 99 | # makeidx 100 | *.idx 101 | *.ilg 102 | *.ind 103 | *.ist 104 | 105 | # minitoc 106 | *.maf 107 | *.mlf 108 | *.mlt 109 | *.mtc 110 | *.mtc[0-9] 111 | *.mtc[1-9][0-9] 112 | 113 | # minted 114 | _minted* 115 | *.pyg 116 | 117 | # morewrites 118 | *.mw 119 | 120 | # mylatexformat 121 | *.fmt 122 | 123 | # nomencl 124 | *.nlo 125 | 126 | # sagetex 127 | *.sagetex.sage 128 | *.sagetex.py 129 | *.sagetex.scmd 130 | 131 | # sympy 132 | *.sout 133 | *.sympy 134 | sympy-plots-for-*.tex/ 135 | 136 | # pdfcomment 137 | *.upa 138 | *.upb 139 | 140 | # pythontex 141 | *.pytxcode 142 | pythontex-files-*/ 143 | 144 | # thmtools 145 | *.loe 146 | 147 | # TikZ & PGF 148 | *.dpth 149 | *.md5 150 | *.auxlock 151 | 152 | # todonotes 153 | *.tdo 154 | 155 | # xindy 156 | *.xdy 157 | 158 | # xypic precompiled matrices 159 | *.xyc 160 | 161 | # endfloat 162 | *.ttt 163 | *.fff 164 | 165 | # Latexian 166 | TSWLatexianTemp* 167 | 168 | ## Editors: 169 | # WinEdt 170 | *.bak 171 | *.sav 172 | 173 | # Texpad 174 | .texpadtmp 175 | 176 | # Kile 177 | *.backup 178 | 179 | # KBibTeX 180 | *~[0-9]* 181 | 182 | # Gummi 183 | */*.tex.swp 184 | -------------------------------------------------------------------------------- /src/public/js/Game/Ball.js: -------------------------------------------------------------------------------- 1 | var Ball = function (game, x, y) { 2 | var self = this; 3 | Phaser.Sprite.call(this, game, x, y, 'ball'); 4 | var initialSpeed = 300; 5 | var speedUpValue = 20; 6 | var initialX = x; 7 | var initialY = y; 8 | var RESET_DELAY_IN_SECONDS = 1.5; 9 | var MAX_VELOCITY = 2000; 10 | 11 | game.physics.p2.enable(this, this.game.properties.debug); 12 | self.body.setCircle(16); 13 | self.body.data.ccdSpeedThreshold = 38.75; 14 | self.body.data.ccdIterations = 10; 15 | 16 | var generateRandomDirection = function () { 17 | var plusOrMinus = Math.random() < 0.5 ? -1 : 1; 18 | return plusOrMinus * ((Math.random() * (0.6 * Math.PI)) + (0.2 * Math.PI)) 19 | }; 20 | 21 | this.start = function () { 22 | self.body.rotation = generateRandomDirection(); 23 | self.body.velocity.x = initialSpeed * Math.sin(self.body.rotation); 24 | self.body.velocity.y = initialSpeed * Math.cos(self.body.rotation); 25 | }; 26 | 27 | this.stop = function () { 28 | self.body.velocity.x = 0; 29 | self.body.velocity.y = 0; 30 | }; 31 | 32 | this.reset = function () { 33 | self.body.x = initialX; 34 | self.body.y = initialY; 35 | self.body.velocity.x = 0; 36 | self.body.velocity.y = 0; 37 | 38 | self.game.time.events.add(Phaser.Timer.SECOND * RESET_DELAY_IN_SECONDS, this.start, this); 39 | }; 40 | 41 | this.speedUp = function () { 42 | var angle = Math.atan2(self.body.velocity.y, self.body.velocity.x); 43 | 44 | var xSpeedUp = Math.cos(angle) * speedUpValue; 45 | var ySpeedUp = Math.sin(angle) * speedUpValue; 46 | self.body.velocity.x = self.body.velocity.x + xSpeedUp; 47 | self.body.velocity.y = self.body.velocity.y + ySpeedUp; 48 | var vx = self.body.velocity.x; 49 | var vy = self.body.velocity.y; 50 | var currVelocitySqr = vx * vx + vy * vy; 51 | if (currVelocitySqr > MAX_VELOCITY * MAX_VELOCITY) { 52 | angle = Math.atan2(vy, vx); 53 | vx = Math.cos(angle) * MAX_VELOCITY; 54 | vy = Math.sin(angle) * MAX_VELOCITY; 55 | self.body.velocity.x = vx; 56 | self.body.velocity.y = vy; 57 | } 58 | console.log("collide! velocity.x:" + self.body.velocity.x + ",self.body.velocity.y : " + self.body.velocity.y + ", xSpeedUp: " + xSpeedUp + ", ySpeedUp: " + ySpeedUp); 59 | }; 60 | 61 | function constrainVelocity(sprite, maxVelocity) { 62 | 63 | vx = self.body.velocity.x; 64 | vy = self.body.velocity.y; 65 | currVelocitySqr = vx * vx + vy * vy; 66 | if (currVelocitySqr > MAX_VELOCITY * MAX_VELOCITY) { 67 | angle = Math.atan2(vy, vx); 68 | vx = Math.cos(angle) * MAX_VELOCITY; 69 | vy = Math.sin(angle) * MAX_VELOCITY; 70 | self.body.velocity.x = vx; 71 | self.body.velocity.y = vy; 72 | } 73 | } 74 | 75 | }; 76 | 77 | Ball.prototype = Object.create(Phaser.Sprite.prototype); 78 | Ball.prototype.constructor = Ball; 79 | 80 | //Interface 81 | module.exports = Ball; 82 | -------------------------------------------------------------------------------- /src/public/js/Game.js: -------------------------------------------------------------------------------- 1 | var Player = require('./Game/Player.js'); 2 | var load = require('./Game/States/Load.js'); 3 | var initializeNewGame = require('./Game/States/InitializeNewGame.js'); 4 | var initial = require('./Game/States/Initial.js'); 5 | var playerConnected = require('./Game/States/PlayerConnected.js'); 6 | var allPlayersConnected = require('./Game/States/AllPlayerConnected.js'); 7 | var gameRunning = require('./Game/States/GameRunning.js'); 8 | var gameEnded = require('./Game/States/GameEnded.js'); 9 | 10 | Phaser.StateManager.prototype.clearCurrentState = function () { 11 | if (this.current) { 12 | if (this.onShutDownCallback) { 13 | this.onShutDownCallback.call(this.callbackContext, this.game); 14 | } 15 | this.game.scale.reset(this._clearWorld); 16 | if (this.game.debug) { 17 | this.game.debug.reset(); 18 | } 19 | if (this._clearWorld) { 20 | this.game.camera.reset(); 21 | this.game.input.reset(true); 22 | this.game.time.removeAll(); 23 | this.game.tweens.removeAll(); 24 | this.game.physics.clear(); 25 | this.game.world.shutdown(); 26 | if (this._clearCache) { 27 | this.game.cache.destroy(); 28 | } 29 | } 30 | } 31 | }; 32 | 33 | var gameProperties = { 34 | name: 'engineTest', 35 | width: 640, 36 | height: 480, 37 | playerIdCount: 0, 38 | players: new Map(), 39 | debug: false, 40 | gameId: undefined, 41 | ball: undefined, 42 | mode: 2, 43 | remainingPlayers: 0 44 | }; 45 | 46 | var game = new Phaser.Game(gameProperties.width, gameProperties.height, Phaser.AUTO, gameProperties.name); 47 | game.properties = gameProperties; 48 | 49 | game.state.add('Load', load); 50 | game.state.add('InitializeNewGame', initializeNewGame); 51 | game.state.add('Initial', initial); 52 | game.state.add('PlayerConnected', playerConnected); 53 | game.state.add('AllPlayersConnected', allPlayersConnected); 54 | game.state.add('GameRunning', gameRunning); 55 | game.state.add('GameEnded', gameEnded); 56 | game.state.start('Load'); 57 | 58 | function addPlayerToGame() { 59 | 60 | var player = game.state.states[game.state.current].addPlayer(); 61 | if (player) { 62 | var playerId = gameProperties.playerIdCount++; 63 | gameProperties.players.set(playerId, player); 64 | game.add.existing(player); 65 | return playerId; 66 | } else { 67 | return undefined; 68 | } 69 | } 70 | 71 | function removePlayerFromGame(playerId) { 72 | game.state.states[game.state.current].removePlayer(playerId); 73 | } 74 | 75 | function addPositionToPlayerBuffer(playerId, x, y) { 76 | player = gameProperties.players.get(playerId); 77 | if (player) { 78 | player.addPositionToBuffer(x, y); 79 | } else { 80 | console.error("Cannot find Player with Id: " + playerId); 81 | } 82 | } 83 | 84 | module.exports.removePlayerFromGame = removePlayerFromGame; 85 | module.exports.addPlayerToGame = addPlayerToGame; 86 | module.exports.addPositionToPlayerBuffer = addPositionToPlayerBuffer; 87 | module.exports.gameProperties = gameProperties; 88 | -------------------------------------------------------------------------------- /src/public/js/Game/States/GameEnded.js: -------------------------------------------------------------------------------- 1 | var abstractState = require('./AbstractState.js'); 2 | 3 | const TIME_UNTIL_RESTART_IN_SECONDS = 10; 4 | 5 | var state = function (game) { 6 | 7 | var self = this; 8 | self.game = game; 9 | abstractState.call(this, game); 10 | 11 | var textStyleTimer = {font: "bold 32px Arial", fill: "#FF0000", boundsAlignH: "center", boundsAlignV: "middle"}; 12 | var textStyleWinner = {font: "bold 32px Arial", fill: "#FFFFFF", boundsAlignH: "center", boundsAlignV: "middle"}; 13 | 14 | this.init = function () { 15 | console.log("GameEnded"); 16 | }; 17 | 18 | this.preload = function () { 19 | // Don't pause game if the form looses focus 20 | this.game.stage.disableVisibilityChange = true; 21 | 22 | //ToDo: Convert to Object, use Anchor 23 | self.circle = game.add.sprite(game.width / 2 - 25, game.height / 2, 'timerBackground'); 24 | 25 | 26 | self.timerText = game.add.text(0, 0, "" + TIME_UNTIL_RESTART_IN_SECONDS, textStyleTimer); 27 | //ToDo: Convert to Object, use Anchor 28 | self.timerText.setTextBounds(self.circle.x, self.circle.y, self.circle.width, self.circle.height); 29 | }; 30 | 31 | this.create = function () { 32 | self.game.properties.ball.destroy(); 33 | 34 | this.printWinner(); 35 | self.timer = game.time.create(); 36 | self.timerEvent = self.timer.add(Phaser.Timer.SECOND * TIME_UNTIL_RESTART_IN_SECONDS, 37 | function () { 38 | this.game.state.states[this.game.state.current].timeout(); 39 | }, this); 40 | 41 | self.timer.start(); 42 | }; 43 | 44 | this.timeout = function () { 45 | // Back to initial state after timeout, 46 | // 2nd parameter is true to reset all objects 47 | this.game.state.start("InitializeNewGame", true, false); 48 | }; 49 | 50 | this.render = function () { 51 | self.timerText.text = Math.round(self.timer.duration / 1000); 52 | }; 53 | 54 | this.shutdown = function () { 55 | self.circle.destroy(); 56 | self.timerText.destroy(); 57 | self.timer.stop(); 58 | self.timer.destroy(); 59 | }; 60 | 61 | this.printWinner = function () { 62 | var cmax = 99; 63 | var cwinner = undefined; 64 | 65 | 66 | self.game.properties.players.forEach(checkPlayer); 67 | 68 | var winner = cwinner == undefined ? "Unentschieden!" : "Spieler: " + cwinner + " hat gewonnen!"; 69 | console.log("Text: " + winner); 70 | 71 | self.textWon = game.add.text(game.width / 2, game.height / 2 - 100, winner, textStyleWinner); 72 | self.textWon.anchor.setTo(0.5, 0.5); 73 | 74 | function checkPlayer(entry) { 75 | console.log("Playerscore " + entry.getPoints() + " - " + cmax); 76 | // Todo: Player Points might need to be reworked 77 | if (entry.getPoints() < cmax) { 78 | cmax = entry.getPoints(); 79 | cwinner = entry.getPlayerName(); 80 | console.log("New Winner " + cwinner); 81 | } else if (entry.getPoints() == cmax) { 82 | cwinner = undefined; 83 | } 84 | console.log("Winner is: " + cwinner); 85 | } 86 | }; 87 | }; 88 | 89 | state.prototype = Object.create(abstractState.prototype); 90 | state.prototype.constructor = state; 91 | 92 | module.exports = state; 93 | -------------------------------------------------------------------------------- /src/public/js/Game/States/InitializeNewGame.js: -------------------------------------------------------------------------------- 1 | var commHandler = require('./../../DisplayPeerCommunicationHandler.js'); 2 | var abstractState = require('./AbstractState.js'); 3 | 4 | 5 | var state = function (game) { 6 | abstractState.call(this, game); 7 | this.init = function () { 8 | console.log("InitializeNewGame"); 9 | }; 10 | this.preload = function () { 11 | // Don't pause game if the form looses focus 12 | this.game.stage.disableVisibilityChange = true; 13 | 14 | //Buttons: 15 | game.load.spritesheet('button2', 'assets/button_2_player.png', 128, 128); 16 | game.load.spritesheet('button3', 'assets/button_3_player.png', 128, 128); 17 | game.load.spritesheet('button4', 'assets/button_4_player.png', 128, 128); 18 | 19 | this.cleanup(); 20 | //@TODO: GameId should be regenerated each game. The if(!this.game.properties.gameId) is just a hotfix, to avoid a bug. 21 | if (!this.game.properties.gameId) { 22 | this.game.properties.gameId = commHandler.init(); 23 | } 24 | }; 25 | this.create = function () { 26 | this.sizeToNormal(); 27 | 28 | // Create Buttons: 29 | this.game.button2Player = game.add.button(game.world.centerX - 160 - 64, 200, 'button2', this.start2Player, this, 2, 1, 0); 30 | this.game.button3Player = game.add.button(game.world.centerX - 64, 200, 'button3', this.start3Player, this, 2, 1, 0); 31 | this.game.button4Player = game.add.button(game.world.centerX + 160 - 64, 200, 'button4', this.start4Player, this, 2, 1, 0); 32 | }; 33 | this.cleanup = function () { 34 | this.game.properties.ball = undefined; 35 | this.game.properties.players = new Map(); 36 | }; 37 | 38 | this.start2Player = function () { 39 | this.destroyButtons(); 40 | this.game.properties.mode = 2; 41 | this.game.properties.remainingPlayers = 2; 42 | this.game.state.start("Initial", false, false, 2); 43 | }; 44 | this.start3Player = function () { 45 | this.destroyButtons(); 46 | this.game.properties.mode = 3; 47 | this.game.properties.remainingPlayers = 3; 48 | this.game.state.start("Initial", false, false, 3); 49 | }; 50 | this.start4Player = function () { 51 | this.destroyButtons(); 52 | this.game.properties.mode = 4; 53 | this.game.properties.remainingPlayers = 4; 54 | this.game.state.start("Initial", false, false, 4); 55 | }; 56 | this.destroyButtons = function () { 57 | this.game.button2Player.destroy(); 58 | this.game.button3Player.destroy(); 59 | this.game.button4Player.destroy(); 60 | }; 61 | this.sizeToNormal = function () { 62 | this.game.width = this.game.properties.width; 63 | this.game.height = this.game.properties.height; 64 | this.game.stage.width = this.game.properties.width; 65 | this.game.stage.height = this.game.properties.height; 66 | if (this.game.renderType === Phaser.WEBGL) { 67 | this.game.renderer.resize(this.game.properties.width, this.game.properties.height); 68 | } 69 | this.game.world.setBounds(0, 0, this.game.properties.width, this.game.properties.height); 70 | this.game.camera.setSize(this.game.properties.width, this.game.properties.height); 71 | this.game.camera.setBoundsToWorld(); 72 | this.game.scale.setShowAll(); 73 | this.game.scale.refresh(); 74 | 75 | } 76 | }; 77 | 78 | 79 | //In case we need to adjust the game view: 80 | 81 | 82 | state.prototype = Object.create(abstractState.prototype); 83 | state.prototype.constructor = state; 84 | 85 | module.exports = state; 86 | -------------------------------------------------------------------------------- /src/public/js/Game/Player.js: -------------------------------------------------------------------------------- 1 | var Player = function (game, fieldInfo, ball, sprite, frame) { 2 | var _ball = ball; 3 | var _fieldInfo = fieldInfo; 4 | var _game = game; 5 | var _frame = frame; 6 | var points = 10; 7 | var maxPoints = points; 8 | var place = placePlayer(fieldInfo); 9 | Phaser.Sprite.call(this, game, place.x, place.y, sprite, _frame); 10 | 11 | // Set Physic 12 | game.physics.p2.enable(this, this.game.properties.debug); 13 | this.body.rotation = fieldInfo.pointWall.body.rotation; 14 | this.body.kinematic = true; 15 | this.body.setRectangle(32, this.height, -16, 0); 16 | 17 | var _buffer = []; 18 | 19 | this.collideWithBall = function (playerBody, ballBody) { 20 | _ball.speedUp(); 21 | }; 22 | 23 | this.body.createBodyCallback(ball, this.collideWithBall, this); 24 | 25 | 26 | this.collideWithPointwall = function (wallBody, ballBody) { 27 | console.log("Hit PointWall, Points: " + points); 28 | if (_game.properties.mode == 2) { 29 | points--; 30 | _fieldInfo.opponentScoreText.text = "" + (maxPoints - points); 31 | if (points <= 0) { 32 | _game.state.start("GameEnded", false, false); 33 | } else { 34 | _ball.reset(); 35 | } 36 | } 37 | else { 38 | if (this.getLives() > 0) { 39 | points--; 40 | 41 | _fieldInfo.opponentScoreText.text = "" + this.getLives(); 42 | if (this.getLives() == 0) { 43 | fieldInfo.pointWall.renderable = true; 44 | fieldInfo.pointWall.move(fieldInfo.path.from, fieldInfo.path.to); 45 | this.destroy(); 46 | if (--_game.properties.remainingPlayers <= 1) { 47 | _game.state.start("GameEnded", false, false); 48 | } 49 | } 50 | _ball.reset(); 51 | } 52 | } 53 | }; 54 | 55 | fieldInfo.pointWall.body.createBodyCallback(ball, this.collideWithPointwall, this); 56 | 57 | this.addPositionToBuffer = function (x, y) { 58 | _buffer.push({"x": x, "y": y}); 59 | }; 60 | 61 | this.update = function () { 62 | while (_buffer.length > 0) { 63 | var pos = _buffer.shift(); 64 | 65 | var path = fieldInfo.path; 66 | 67 | var dis = Phaser.Math.distance(path.from.x, path.from.y, path.to.x, path.to.y); 68 | var dx = ((path.from.x - path.to.x)) / dis; 69 | var dy = ((path.from.y - path.to.y)) / dis; 70 | 71 | var mM = Math.min(pos.y + this.height / 2, dis - this.height / 2); 72 | 73 | // Movement is working. 74 | this.body.x = path.to.x + mM * dx; 75 | this.body.y = path.to.y + mM * dy; 76 | } 77 | }; 78 | 79 | this.getPoints = function () { 80 | return maxPoints - points; 81 | }; 82 | 83 | this.getLives = function () { 84 | return points; 85 | }; 86 | 87 | this.getPlayerName = function () { 88 | var names = ["Grau", "Rot", "Grün", "Blau"]; 89 | return names[_frame]; 90 | }; 91 | 92 | 93 | }; 94 | 95 | function placePlayer(fieldInfo) { 96 | var path = fieldInfo.path; 97 | 98 | var dis = Phaser.Math.distance(path.from.x, path.from.y, path.to.x, path.to.y); 99 | var dx = ((path.from.x - path.to.x)) / dis; 100 | var dy = ((path.from.y - path.to.y)) / dis; 101 | 102 | var mM = dis / 2; 103 | 104 | return {"x": path.to.x + mM * dx, "y": path.to.y + mM * dy}; 105 | }; 106 | 107 | Player.prototype = Object.create(Phaser.Sprite.prototype); 108 | Player.prototype.constructor = Player; 109 | 110 | //Interface 111 | module.exports = Player; 112 | -------------------------------------------------------------------------------- /docu/maindocumentation/JavaScript.tex: -------------------------------------------------------------------------------- 1 | \chapter{JavaScript} 2 | \section{Auswahl einer Physics Engine} 3 | Die Darstellung auf dem Anzeigegerät ist über den DOM nicht möglich, da die Bewegung des Balls und der Schläger zu schnell werden. Zudem wird auch die Physik auf dem Anzeigegerät berechnet. Hierfür ist eine Kollisionserkennung erforderlich. 4 | 5 | \subsection{Anforderungen} 6 | \begin{itemize} 7 | \item \textbf{JavaScript Game Engine} \newline 8 | Da wir im Backend einen NodeJS Server stehen haben und im Frontend ebenfalls JavaScript nutzen, ergibt es sich von selbst, dass wir eine JavaScript Game Engine brauchen. 9 | \item \textbf{Unterstützung von 2D Graphics} \newline 10 | Wir wollen unser Spiel in 2D umsetzen, insofern ist 3D-Unterstützung nicht notwendig. 11 | \item \textbf{Physik} \newline 12 | Nicht jede Game Engine unterstützt auch Physik, in unserem Spiel sind allerdings physikalische Gegebenheiten zu beachten wie zB der richtige Ein- und Austrittswinkel des Balls. Da der thematische Schwerpunkt unseres Projektes auf den browserspezifischen Gegebenheiten liegt, wollen wir uns nicht mit umfangreicher Implementierung der Physik beschäftigen. 13 | \item \textbf{Kollisionserkennung} \newline 14 | Ebensowenig unterstützt jede Physics Engine auch Kollisionserkennung. Diese ist allerdings ein zentraler Punkt des Spiels, sowohl die Kollisionen des Balls mit den Schlägern, als auch mit dem Spielfeldrand und später ggf. weiteren Bällen. 15 | \item \textbf{Opensource} \newline 16 | Wir sind alle arme Studenten und wollen bzw können daher nicht Geld für unser Uniprojekt ausgeben. Hinzu kommt, dass man bei Opensource-Projekten den Sourcecode einsehen kann, was in vielen Situationen hilfreich ist. 17 | \item \textbf{Performance} \newline 18 | Auch wenn wir nur ein sehr kleines Spiel bauen, so soll es doch so gut wie möglich in Echtzeit reagieren können, da Verzögerungen sofort auffallen. Die Physics Engine muss somit auch schnell die jeweiligen Positionen der Spielobjekte berechnen und darstellen können. 19 | \item \textbf{Dokumentation} \newline 20 | Eine vorhandene und idealerweise auch gute Dokumentation lässt die Lernkurve zu Beginn steiler sein und ist auch bei später ggf. auftretenden Problemen wünschenswert. 21 | \item \textbf{Community} \newline 22 | Wir wollen eine Engine aussuchen, die auch wirklich genutzt wird, und bei der es idealerweise auch einen entsprechenden Support aus der Community gibt. Dies ist ebenfalls nützlich im Hinblick auf zukünftige Schwierigkeiten. 23 | \end{itemize} 24 | 25 | \subsection{Verglichene Engines} 26 | Die meisten Engines mussten nur kurz überflogen werden, da sie mindestens eine der Anforderungen nicht erfüllt haben. Es gab natürlich noch weitere, allerdings schien bei denen keine große Community dahinter zu stehen, was uns in vergangenen Projekten schon auf die Füße gefallen ist. 27 | \begin{itemize} 28 | \item \textbf{Construct 2} \newline 29 | Construct 2 ist zwar vermeintlich kostenlos, dabei steht allerdings nur eine Demo zur Verfügung. Zusätzlich gefällt die Handhabung nicht. 30 | \item \textbf{ImpactJS} \newline 31 | Kostet 99 USD und ist somit direkt raus. 32 | \item \textbf{EaselJS} \newline 33 | Ist zwar kostenlos, bietet aber weder Physik- noch Kollisionsunterstützung an. 34 | \item \textbf{pixi.js} \newline 35 | Ist zwar kostenlos, bietet aber weder Physik- noch Kollisionsunterstützung an. 36 | \item \textbf{Phaser} \newline 37 | Erfüllt alle Anforderungen und hat zusätzlich zu der guten Dokumentation noch viele brauchbare Beispiele, an denen man sich orientieren kann. Hinzu kommt, dass sogar 3 verschiedene Physiksysteme unterstützt werden. Dabei ist Arcade Physics eine sehr leichtgewichtige Variante, die auch auf ressourcenarmen Geräten läuft und für unser Spiel vollkommen ausreichend ist. 38 | \end{itemize} 39 | 40 | \subsection{Entscheidung} 41 | Nach Rücksprache mit den Teammitgliedern ist die Auswahl entsprechend obigen Kriterien auf Phaser gefallen. 42 | -------------------------------------------------------------------------------- /docu/maindocumentation/architecture/prototypSequenceDiagram.xml: -------------------------------------------------------------------------------- 1 | 7V1tb6s2FP41kboPN8IYMHxsb9vtSt1WrVfa9pEGJ0EjEBFy2+7Xzw4Ygm3AJJiUdpOmCw4v6XMe2+c859iZwa+b159Tf7v+NQlwNDON4HUGb2emaSJkk39oy1veAkyvaFmlYVC0VQ1P4b+4aDSK1n0Y4F3twixJoizc1hsXSRzjRVZr89M0ealftkyi+lu3/goLDU8LPxJb/wyDbJ23uqZTtf+Cw9WavRk4Xv7Js7/4Z5Um+7h438yEy8N/+ccbnz2r+EN3az9IXo6a4N0Mfk2TJMuPNq9fcUTBZbDl9903fFp+7xTHmcoNjo/9BQrwEplL7GDwxSr+kB9+tC/AmMHrx8h/w2nxlbM3BhP59lt6uN9ED+ESR2FMzm62OA03OCPXw9uoaH6s2m5e1mGGn7b+gt76QhhE2tbZJiJngBwSm2Y+uSUtz6PI3+7C58NbDdKS4sU+3YU/8B94l1OHtib7jL7pa0kJ43DzJlwUDyr+MJxm+LURLlAagbAbJ+Q7p2/kEnaD4RX3FMz23Pz0pWIJ8Arbro8ZYjtFq19Qc1U+vDIPOSgspGgtG4jW4o10BO02CePs8H77ZmbfcrZK0mydrJLYj46t1RvBBko1YsrgYogiU4RUgijUgadjfTg8AQOqA09LB56kv3w4QC3bUwK0mpAGRRTYHw5RE6p1edPVAKgFu/EkjyCeB53b2Iy3iJJ9oDKXDTr9IAPWgINIxM2UzT4IaOndktkHXs9MJ8ooVFs/Jscrekwm5SxNCMlwWszPYRL/4sdBRL2K/Hry+uNb/nc2LGFmlPgatszXsHVYGzoTGHhAO6Q8oqbEfZP0H1cLnq6A54+EXN2CKUUjJFHKdRSuYtL2nGRZsiEf4Di4pmEPRXaLyUc3gb9b46C4j3xcxFguPXsNs78KytLjv+nxnDCJgY4DLjraJft0gTt4cZJdUhz5GelctdfJYC5ufaSsquz5hXd2DMOqPyPz0xXOits4a5Xf48SZAwkGXKz9eIUfkx0d366esjSMV+SKPHz6RmxL3mL8tt8802HP+Is7//unk4y/I39jVpqffBvWdh9GUcUAdsVzlCz+YZcc04IM0W8HXswBO82pYSCbNRz11gN/GgijwAUS0R9M0wSuISfMUU9lEdWZHCKcqQ8Ktll/RM5+gULig5yOB2nkoi0OJtMbnOvOjacWt2gKXEyZa3NPujNOSfcOa/7LZ3dUoNntqFhSUUSPozKFID4nWCOkjl1D1LXUYk4tjor98fAkY75ayKkHUNFxmDqgjtpgrUe0m4LG1A9OqCjamTrwRFNQmPrhiRy18VMLP9kY8oHwtAxbjZ+2BjxNyfAJr29o1g+n14/fBHA/nUNWelu9HTItWSo4hQ6Qs0p5QIEXHFDgFLJ+PfH01PwxLfxEU/DH+uEJbTUHQguepuhABOFuG/lv35P9Yo2Dq+GELhVhq9A7wRzWFU/Izk9QtZSU0QYNi4mXxpyplWdKWOUww8JFh3uEqoQFXS7udJz6gxokrOpB7MJkudzhwWUulig8Ytbh6+SCzEmkkhCoVELrOmgzMVqVTKg+fZ9JA+iiuvVcT4kGg1hGTKOmONunxCjG1dCK90npDiCmO0pDf+E17556d/sw0Dx8tBPH0sISE9X7uGep9fFBWCLOtDtiLdaBdRJF6NyGYHAJUUC/GaLdnpJEmRJ/9PDA4vIewOCjNZ3DhbTM8Db3EmQVBP+XDvQJAF27OwCUlylqUeStKQSAsF+ZonXB0gFnCgFgPzwBUAtYPC14TiEA7MCT+Xqspk5WmTyWguxNIWPUD08APDXFR8sA6kmmy6kDCs0LSmhl0ceUERUqvRX7vKZSb3FSSvEm+YGv2OoRY3s4EF156kc/FacVlndV67Hf5RdOfoSXhZO4COPVw+Hs1pQ7/YcVOYf4sPT6B4vvSyqdZMqBi5cAZNXpHU48QYg62uVlBcGbXwQbXtRYDSXcULCzIlj+HYargRLFIT8oY8vvyc1+ucTpZWPMBoLp14Y8tlSojPYcNaKIhgUumPPlFI4754JHncVuotZ08crZakzhqyedc4onzymVPHdoAYCjDLDUbKxGGU+gjEa9wRZTEiS8J0QxjSI18W13lJzIZYXnVEVRuE/9DVaRCypvgM3PdLEGuYhNzZat6im0R/9cHaqLLMEtcKSeFlt6N2y1k+i7Usj9bLG+2Hjb2p9YLW1fna6phOLMfmiDel0sYCr/CN3GEafUYsn1iSk87cZzRptRHc71Mo0R7SIOZzF+ebc2scbzcjibmCPaRFwfVWXAOkOg977K54I2dMbzLJ2Le5ZzWZpSPQHVbkPUOLEpTIiDG9pFnKFdqGTo5tB1UCqIAtHYVIA2qpPBq63TEiINozHSGIpBLa5R+wygR/2wOI0BOkiJQUPwAymsGx9bQOxYpsMV9wCT7WfTlcOymg1zetGdmCN48cPsikazxoZ0Z+ML+f/uFS/22Z7qNwSWpzBe4Ad/l/1Jrvw9faKFXsWF9N6MXPI7gejudUs8VBzQW2gw529oLBY/77ayUG7S4iNj4QjFRR7HHoC4upGBlEa+U5cvalIXhBsYr7UpjUhcbZniVbgj3fb2qNDxgbbEONXgl4vVSlIHAswNgPqN/+10a/Yg2j0PPeM/X8pkWeO5+0iUNcb1D9rq2XSWs7VwoJU7rh6ZkueAPeIOAK6Y8mKqYpaSlonriHxVMRDdBceR5Re1VJW7ohY1g9c/U9BaUP0c9V4AsoK63gVfemw1hQIQt0Gvbdikisl9lyhX8Kaw904HnlxxOjAvuaYdTGI3oy6GmhyiSA1RPfUfktruvP4j1z53T9uUjK/3abKhY7bEJ550IAaUtkFqMui5kinftXQVgZg84+pFIMNHWq6Y6KA1HQdGfU+kk/+poVW5Soz8WfUQyvD6hVC9oyMl97lhFcBRX2cj5cACPIRqUZWkBKjrSTp984vFZ1UQ5iGHU2iR1ybRsqVIDqf79kwC9E5cswxzu4bbsAD13DITi62POpduHtuopnyQNxrbPOmS1/Ny5WcudzXmdFH1MY9cuyeRWvnAirz1S4/Iqwc3oJzwBp7fPL6a0vQ0K4leu9M0eN64xyq4uWV69WHI67leup0+J66G8/SshiNzlVO3PeTXRWoUE70Pshdoi1HbyaCpOlowqsU82BHmBMD2GBMVq6v9NvAz/JAkW7FLfz71qqxGfB/qFTDeYW63iV6q+hU01MQBoGUBE5BtqDl1SB1FvQW1DFxnkFR0HT5S8sHjqoMsSbGCB6Xqlh64xSl6maR3/mJ9HQQ4YI7axEHnf+vClYBuyFRvTT92IRZh5TPnqCl1zgvuqQZ1LB7zGj2m9pg9n6OGd5lMbqGQbY/nBpdCxHuaJbq22jPrwSm0Jc7LaCI8ENMaO5zl0vtV/g9F83AwQhc6qUuwX7jRLywQ49WVczhivTgA4gT+ripIchXySLyEpbw5RB1Js507CaJp6LO4fczLrjxGsCjZJLPmXxTLWL/F+TLW8TwNVPc0jIHGTYPT2mwkjptSVwMgPenLd7inSgeEwK2rGw685Pp/+d5813FwiyMCACMwTQAXK7E/XAp4tLVnAHC/Q+kA7rfRBsv4Akf6Im2KOJDsJDi5jnjRjTgkv39R+oDVbgifoT+OVhwv9kc0Vn9E2vujmNoc2U2dm4BfCKU7I87C8g5HtEEuHNoRRYpLviU/ScVVYCBvqM0+yWn1M9X55dWPgcO7/wA= -------------------------------------------------------------------------------- /src/public/js/FingerPositionHandler.js: -------------------------------------------------------------------------------- 1 | //Interface 2 | module.exports = FingerPositionHandler; 3 | /** 4 | * String canvas: Die ID des Canvas, das überwacht werden soll 5 | * Integer interval: Zeit in Millisekunden, die zwischen den Calls der Callbacks liegen sollte 6 | * List= (_interval - _lastSendDiff) || _lastSendTime === undefined){ 48 | for (var i = 0, len = callbackFunctions.length; i < len; i++) { 49 | callbackFunctions[i](_x, _y); 50 | } 51 | if(_lastSendTime){ 52 | _lastSendDiff = (currentTime - _lastSendTime) - _interval; 53 | if(_lastSendDiff < 0){ 54 | _lastSendDiff = 0; 55 | } 56 | } 57 | _lastSendTime = currentTime; 58 | } 59 | } 60 | } 61 | // Prevent iPad from scrolling 62 | event.preventDefault(); 63 | } 64 | 65 | /** 66 | * Wird aufgerufen, wenn die Mouse- oder Toucheingabe endet 67 | */ 68 | function processInputEnd(event){ 69 | //document.getElementById("mouseInfo").innerHTML += "
processInputEnd" 70 | _lastSendTime = undefined; 71 | _lastSendDiff = 0; 72 | _processMouseInformations = false; 73 | // Prevent iPad from scrolling 74 | event.preventDefault(); 75 | } 76 | 77 | /** 78 | * Bestimmt die Position des Canvas in dem Browser. 79 | * Diese wird benötigt, da die Position im Mouseevent absolut ist. 80 | */ 81 | function getOffset(obj) { 82 | var offsetLeft = 0; 83 | var offsetTop = 0; 84 | do { 85 | if (!isNaN(obj.offsetLeft)) { 86 | offsetLeft += obj.offsetLeft; 87 | } 88 | if (!isNaN(obj.offsetTop)) { 89 | offsetTop += obj.offsetTop; 90 | } 91 | } while(obj = obj.offsetParent ); 92 | return {left: offsetLeft, top: offsetTop}; 93 | } 94 | 95 | var _offset = getOffset(_touchzone); 96 | /** 97 | * Register Listener 98 | */ 99 | _touchzone.addEventListener("mousedown", processInputStart, false); 100 | _touchzone.addEventListener("touchstart", processInputStart, false); 101 | 102 | _touchzone.addEventListener("touchmove", processInput, false); 103 | _touchzone.addEventListener("mousemove", processInput, false); 104 | 105 | _touchzone.addEventListener("touchend", processInputEnd, false); 106 | _touchzone.addEventListener("mouseup", processInputEnd, false); 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/public/js/DisplayPeerCommunicationHandler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Handler for Communication from and to the Display Peer. 3 | * The Display Peer represents the Monitor. 4 | * 5 | * For further information: 6 | * 7 | * Sequence including this Entity: 8 | * https://raw.githubusercontent.com/Transport-Protocol/MBC-Ping-Pong 9 | * /master/docu/maindocumentation/architecture/prototypSequenceDiagram.png 10 | * 11 | * Section in documentation: 12 | * Chapter: Architecture 13 | * Section: Prototyp 14 | */ 15 | 16 | var io = require('socket.io-client'); 17 | var sigclient = require('./../../own_modules/WebRTC-COMM.js').Client; 18 | var UUIDGEN = require('./../../own_modules/WebRTC-COMM.js').getID; 19 | 20 | // Interface 21 | module.exports.init = init; 22 | module.exports.addPositionToPlayerBufferListener = addPositionToPlayerBufferListener; 23 | module.exports.addPlayerToGameListener = addPlayerToGameListener; 24 | module.exports.removePlayerFromGameListener = removePlayerFromGameListener; 25 | 26 | var opts = { "mode":"multi", "maxpeers":999 }; 27 | 28 | // Predefined vars used in init 29 | var ioSocket; 30 | var client; 31 | 32 | // Statecheck replacers 33 | // @TODO replace with states 34 | var isConnected = false; 35 | 36 | // List of listener functions to call 37 | var listener_addPosition = []; 38 | var listener_remPlayer = []; 39 | var listener_single_addPlayer; 40 | 41 | // List Object of all Connected players. 42 | // Key: The peer id of the Players socket. 43 | // Value: The player id the game has given. 44 | var playerconn = {}; 45 | 46 | 47 | function init(){ 48 | ioSocket = io.connect(); 49 | 50 | var gameid = UUIDGEN(); 51 | 52 | console.log("GAME ID: " + gameid); 53 | 54 | 55 | ioSocket.on('connect', function(){ 56 | console.log("Now Connected to the Server."); 57 | 58 | if( !isConnected ) { 59 | client = new sigclient(ioSocket, opts, gameid, null); 60 | client.setNewConnectionCallback(gotNewConnection); 61 | client.setDisconnectCallback(gotDisconnectedPlayer); 62 | client.setMessageCallback(gotNewMessage); 63 | } 64 | 65 | isConnected = true; 66 | }); 67 | 68 | return gameid; 69 | } 70 | 71 | function displayPlayers(){ 72 | for (var key in playerconn) { 73 | if (playerconn.hasOwnProperty(key)) { 74 | var obj = playerconn[key]; 75 | console.log("ID: <" + obj + "> >> Peer: [" + key + "]"); 76 | } 77 | } 78 | } 79 | 80 | function gotNewConnection(peerid) { 81 | console.log("New Player connected [" + peerid + "]") 82 | 83 | var id = addPlayer(); 84 | 85 | playerconn[peerid] = id; 86 | 87 | displayPlayers(); 88 | } 89 | 90 | function gotNewMessage(type, peerid, message) { 91 | console.log("type: " + type); 92 | console.log("from: " + peerid); 93 | 94 | if( type === "changeposition" ) { 95 | 96 | console.log("MSG: " + message); 97 | var positions = JSON.parse(message); 98 | 99 | // Received a position change 100 | if( playerconn.hasOwnProperty(peerid) ) { 101 | 102 | var id = playerconn[peerid]; 103 | 104 | addPos(id, positions.posX, positions.posY); 105 | 106 | console.log("User ["+ peerid +"] changed Position."); 107 | } else { 108 | 109 | // @TODO for testing purpose, states seem weird and undefined 110 | gotNewConnection(peerid); 111 | 112 | var id = playerconn[peerid]; 113 | 114 | addPos(id, message.posX, message.posY); 115 | 116 | console.log("User ["+ peerid +"] changed Position."); 117 | } 118 | } 119 | } 120 | 121 | function gotDisconnectedPlayer(peerid){ 122 | // Extract data from Object 123 | var playerid = playerconn[peerid]; 124 | 125 | // Call all registered methods with given parameters 126 | listener_remPlayer.forEach(function(cb){ 127 | cb(playerid); 128 | }); 129 | 130 | // Delete Key of disconnected Player 131 | delete playerconn[peerid]; 132 | } 133 | 134 | function addPos(playerId, posX, posY){ 135 | // if( playerId in playerconn ) return; 136 | 137 | // Call all registered methods with given parameters 138 | listener_addPosition.forEach(function(cb){ 139 | cb(playerId, posX, posY); 140 | }); 141 | } 142 | 143 | function addPlayer(){ 144 | return listener_single_addPlayer(); 145 | } 146 | 147 | // EXPORTED 148 | function addPositionToPlayerBufferListener(cb){ 149 | listener_addPosition.push(cb); 150 | } 151 | 152 | // EXPORTED 153 | function addPlayerToGameListener(cb) { 154 | listener_single_addPlayer = cb; 155 | } 156 | 157 | // EXPORTED 158 | function removePlayerFromGameListener(cb){ 159 | listener_remPlayer.push(cb); 160 | } 161 | -------------------------------------------------------------------------------- /docu/maindocumentation/MBC-Ping-Pong_Backend_only.tex: -------------------------------------------------------------------------------- 1 | \documentclass[draft=false 2 | ,paper=a4 3 | ,twoside=false 4 | ,fontsize=11pt 5 | ,headsepline 6 | ,BCOR10mm 7 | ,DIV11 8 | ]{scrbook} 9 | \usepackage[ngerman,english]{babel} 10 | %% see http://www.tex.ac.uk/cgi-bin/texfaq2html?label=uselmfonts 11 | \usepackage[T1]{fontenc} 12 | \usepackage[utf8]{inputenc} 13 | %\usepackage[latin1]{inputenc} 14 | \usepackage{libertine} 15 | \usepackage{pifont} 16 | \usepackage{microtype} 17 | \usepackage{textcomp} 18 | \usepackage[german,refpage]{nomencl} 19 | \usepackage{setspace} 20 | \usepackage{makeidx} 21 | \usepackage{listings} 22 | \usepackage{natbib} 23 | \usepackage[ngerman,colorlinks=true]{hyperref} 24 | \usepackage{soul} 25 | \usepackage{hawstyle} 26 | \usepackage{lipsum} %% for sample text 27 | \usepackage{tocbibind} 28 | \usepackage{amsmath} 29 | \usepackage{amssymb} 30 | \usepackage{hyperref} 31 | \usepackage[section]{placeins} 32 | \usepackage{grffile} 33 | \usepackage{float} 34 | 35 | %% define some colors 36 | \colorlet{BackgroundColor}{gray!20} 37 | \colorlet{KeywordColor}{blue} 38 | \colorlet{CommentColor}{black!60} 39 | %% for tables 40 | \colorlet{HeadColor}{gray!60} 41 | \colorlet{Color1}{blue!10} 42 | \colorlet{Color2}{white} 43 | 44 | %% configure colors 45 | \HAWifprinter{ 46 | \colorlet{BackgroundColor}{gray!20} 47 | \colorlet{KeywordColor}{black} 48 | \colorlet{CommentColor}{gray} 49 | % for tables 50 | \colorlet{HeadColor}{gray!60} 51 | \colorlet{Color1}{gray!40} 52 | \colorlet{Color2}{white} 53 | }{} 54 | \lstset{% 55 | numbers=left, 56 | numberstyle=\tiny, 57 | stepnumber=1, 58 | numbersep=5pt, 59 | basicstyle=\ttfamily\small, 60 | keywordstyle=\color{KeywordColor}\bfseries, 61 | identifierstyle=\color{black}, 62 | commentstyle=\color{CommentColor}, 63 | backgroundcolor=\color{BackgroundColor}, 64 | captionpos=b, 65 | fontadjust=true 66 | } 67 | \lstset{escapeinside={(*@}{@*)}, % used to enter latex code inside listings 68 | morekeywords={uint32_t, int32_t} 69 | } 70 | \ifpdfoutput{ 71 | \hypersetup{bookmarksopen=false,bookmarksnumbered,linktocpage} 72 | }{} 73 | 74 | %% more fancy C++ 75 | \DeclareRobustCommand{\cxx}{C\raisebox{0.25ex}{{\scriptsize +\kern-0.25ex +}}} 76 | 77 | \clubpenalty=10000 78 | \widowpenalty=10000 79 | \displaywidowpenalty=10000 80 | 81 | % unknown hyphenations 82 | \hyphenation{ 83 | } 84 | 85 | %% recalculate text area 86 | \typearea[current]{last} 87 | 88 | \makeindex 89 | \makenomenclature 90 | 91 | \begin{document} 92 | \selectlanguage{ngerman} 93 | 94 | %%%%% 95 | %% customize (see readme.pdf for supported values) 96 | \HAWThesisProperties{Author={Andreas Müller} 97 | ,Title={MBC-Ping-Pong Backend} 98 | ,EnglishTitle={MBC-Ping-Pong Backend} 99 | ,ThesisType={Projektarbeit} 100 | ,ExaminationType={Wahlpflichtfach} 101 | ,DegreeProgramme={Bachelor of Science Angewandte Informatik} 102 | ,ThesisExperts={Prof. Dr. Martin Becke} 103 | ,ReleaseDate={\today} 104 | } 105 | 106 | %% title 107 | \frontmatter 108 | 109 | %% output title page 110 | \maketitle 111 | 112 | \onehalfspacing 113 | 114 | %% add abstract pages 115 | %% note: this is one command on multiple lines 116 | \HAWAbstractPage 117 | %% German abstract 118 | {Ping-Pong, NodeJS, JavaScript, WebRTC}% 119 | {In diesem Dokument wird das Projekt im MBC-Ping-Pong, das im Rahmen des Wahlpflichtfaches Modernebrowserkommunikation an der HAW-Hamburg erstellt wird, behandelt. Dieses Dokument behandelt das Backend und dem damit entstandenen Kommunikationsmodul. } 120 | %% English abstract 121 | {Ping-Pong, NodeJS, JavaScript, WebRTC}% 122 | {This document is about the Project MBC-Ping-Pong, which is made for the elective course Modernebrowserkommunikation at HAW-Hamburg. This document is about the Backend and the thus implemented communication module. } 123 | 124 | \newpage 125 | \singlespacing 126 | 127 | \tableofcontents 128 | \newpage 129 | %% enable if these lists should be shown on their own page 130 | \listoftables 131 | \listoffigures 132 | %%\lstlistoflistings 133 | 134 | %% main 135 | \mainmatter 136 | \onehalfspacing 137 | %% write to the log/stdout 138 | \typeout{===== File: chapter 1} 139 | %% include chapter file (chapter1.tex) 140 | %%\include{chapter1} 141 | 142 | 143 | \include{Backend} 144 | 145 | See also \cite{sample_bib}. 146 | 147 | 148 | %%%% 149 | 150 | %% appendix if used 151 | %%\appendix 152 | %%\typeout{===== File: appendix} 153 | %%\include{appendix} 154 | 155 | % bibliography and other stuff 156 | \backmatter 157 | 158 | \typeout{===== Section: literature} 159 | %% read the documentation for customizing the style 160 | \bibliographystyle{dinat} 161 | \bibliography{MBC-Ping-Pong_Backend_only} 162 | 163 | \typeout{===== Section: nomenclature} 164 | %% uncomment if a TOC entry is needed 165 | %%\addcontentsline{toc}{chapter}{Glossar} 166 | \renewcommand{\nomname}{Glossar} 167 | \clearpage 168 | \markboth{\nomname}{\nomname} %% see nomencl doc, page 9, section 4.1 169 | \printnomenclature 170 | 171 | %% index 172 | \typeout{===== Section: index} 173 | \printindex 174 | 175 | \HAWasurency 176 | 177 | \end{document} 178 | -------------------------------------------------------------------------------- /docu/maindocumentation/MBC-Ping-Pong.tex: -------------------------------------------------------------------------------- 1 | \documentclass[draft=false 2 | ,paper=a4 3 | ,twoside=false 4 | ,fontsize=11pt 5 | ,headsepline 6 | ,BCOR10mm 7 | ,DIV11 8 | ]{scrbook} 9 | \usepackage[ngerman,english]{babel} 10 | %% see http://www.tex.ac.uk/cgi-bin/texfaq2html?label=uselmfonts 11 | \usepackage[T1]{fontenc} 12 | \usepackage[utf8]{inputenc} 13 | %\usepackage[latin1]{inputenc} 14 | \usepackage{libertine} 15 | \usepackage{pifont} 16 | \usepackage{microtype} 17 | \usepackage{textcomp} 18 | \usepackage[german,refpage]{nomencl} 19 | \usepackage{setspace} 20 | \usepackage{makeidx} 21 | \usepackage{listings} 22 | \usepackage{natbib} 23 | \usepackage[ngerman,colorlinks=true]{hyperref} 24 | \usepackage{soul} 25 | \usepackage{hawstyle} 26 | \usepackage{lipsum} %% for sample text 27 | \usepackage{tocbibind} 28 | \usepackage{amsmath} 29 | \usepackage{amssymb} 30 | \usepackage{hyperref} 31 | \usepackage[section]{placeins} 32 | \usepackage{grffile} 33 | \usepackage{float} 34 | 35 | %% define some colors 36 | \colorlet{BackgroundColor}{gray!20} 37 | \colorlet{KeywordColor}{blue} 38 | \colorlet{CommentColor}{black!60} 39 | %% for tables 40 | \colorlet{HeadColor}{gray!60} 41 | \colorlet{Color1}{blue!10} 42 | \colorlet{Color2}{white} 43 | 44 | %% configure colors 45 | \HAWifprinter{ 46 | \colorlet{BackgroundColor}{gray!20} 47 | \colorlet{KeywordColor}{black} 48 | \colorlet{CommentColor}{gray} 49 | % for tables 50 | \colorlet{HeadColor}{gray!60} 51 | \colorlet{Color1}{gray!40} 52 | \colorlet{Color2}{white} 53 | }{} 54 | \lstset{% 55 | numbers=left, 56 | numberstyle=\tiny, 57 | stepnumber=1, 58 | numbersep=5pt, 59 | basicstyle=\ttfamily\small, 60 | keywordstyle=\color{KeywordColor}\bfseries, 61 | identifierstyle=\color{black}, 62 | commentstyle=\color{CommentColor}, 63 | backgroundcolor=\color{BackgroundColor}, 64 | captionpos=b, 65 | fontadjust=true 66 | } 67 | \lstset{escapeinside={(*@}{@*)}, % used to enter latex code inside listings 68 | morekeywords={uint32_t, int32_t} 69 | } 70 | \ifpdfoutput{ 71 | \hypersetup{bookmarksopen=false,bookmarksnumbered,linktocpage} 72 | }{} 73 | 74 | %% more fancy C++ 75 | \DeclareRobustCommand{\cxx}{C\raisebox{0.25ex}{{\scriptsize +\kern-0.25ex +}}} 76 | 77 | \clubpenalty=10000 78 | \widowpenalty=10000 79 | \displaywidowpenalty=10000 80 | 81 | % unknown hyphenations 82 | \hyphenation{ 83 | } 84 | 85 | %% recalculate text area 86 | \typearea[current]{last} 87 | 88 | \makeindex 89 | \makenomenclature 90 | 91 | \begin{document} 92 | \selectlanguage{ngerman} 93 | 94 | %%%%% 95 | %% customize (see readme.pdf for supported values) 96 | \HAWThesisProperties{Author={Andreas Müller, Claus Torben Haug, Jan Dennis Bartels, Marjan Bachtiari} 97 | ,Title={MBC-Ping-Pong} 98 | ,EnglishTitle={MBC-Ping-Pong} 99 | ,ThesisType={Projektarbeit} 100 | ,ExaminationType={Wahlpflichtfach} 101 | ,DegreeProgramme={Bachelor of Science Angewandte Informatik} 102 | ,ThesisExperts={Prof. Dr. Martin Becke} 103 | ,ReleaseDate={\today} 104 | } 105 | 106 | %% title 107 | \frontmatter 108 | 109 | %% output title page 110 | \maketitle 111 | 112 | \onehalfspacing 113 | 114 | %% add abstract pages 115 | %% note: this is one command on multiple lines 116 | \HAWAbstractPage 117 | %% German abstract 118 | {Ping-Pong, NodeJS, JavaScript, WebRTC}% 119 | {In diesem Dokument wird das Projekt im MBC-Ping-Pong, das im Rahmen des Wahlpflichtfaches Modernebrowserkommunikation an der HAW-Hamburg erstellt wird, behandelt. Hierbei handelt es sich um eine Pong Clone welcher auf einem zentralen Bildschirm spielbar ist und mnittels WebRTC gesteuert wird. Es wird auf die Architektur, JavaScript, Frontend und Backend eingegangen. } 120 | %% English abstract 121 | {Ping-Pong, NodeJS, JavaScript, WebRTC}% 122 | {This document is about the Project MBC-Ping-Pong, which is made for the elective course Modernebrowserkommunikation at HAW-Hamburg. Its about a Pong clone, played on a central screen nd controled via WebRTC. The architecture, JavaScript, frontend and backend are discussed. } 123 | 124 | \newpage 125 | \singlespacing 126 | 127 | \tableofcontents 128 | \newpage 129 | %% enable if these lists should be shown on their own page 130 | \listoftables 131 | \listoffigures 132 | %%\lstlistoflistings 133 | 134 | %% main 135 | \mainmatter 136 | \onehalfspacing 137 | %% write to the log/stdout 138 | \typeout{===== File: chapter 1} 139 | %% include chapter file (chapter1.tex) 140 | %%\include{chapter1} 141 | 142 | \include{Architektur} 143 | 144 | \include{JavaScript} 145 | 146 | \include{Backend} 147 | 148 | \include{Frontend} 149 | See also \cite{sample_bib}. 150 | 151 | 152 | %%%% 153 | 154 | %% appendix if used 155 | %%\appendix 156 | %%\typeout{===== File: appendix} 157 | %%\include{appendix} 158 | 159 | % bibliography and other stuff 160 | \backmatter 161 | 162 | \typeout{===== Section: literature} 163 | %% read the documentation for customizing the style 164 | \bibliographystyle{dinat} 165 | \bibliography{MBC-Ping-Pong} 166 | 167 | \typeout{===== Section: nomenclature} 168 | %% uncomment if a TOC entry is needed 169 | %%\addcontentsline{toc}{chapter}{Glossar} 170 | \renewcommand{\nomname}{Glossar} 171 | \clearpage 172 | \markboth{\nomname}{\nomname} %% see nomencl doc, page 9, section 4.1 173 | \printnomenclature 174 | 175 | %% index 176 | \typeout{===== Section: index} 177 | \printindex 178 | 179 | \HAWasurency 180 | 181 | \end{document} 182 | -------------------------------------------------------------------------------- /src/public/js/Game/Wallbuilder.js: -------------------------------------------------------------------------------- 1 | var Wall = require('./Wall.js'); 2 | 3 | 4 | var field2Player = { 5 | walls: [{from: {x: 0, y: 64}, to: {x: 640, y: 64}}, 6 | {from: {x: 640, y: 440}, to: {x: 0, y: 440}}], 7 | scoreWalls: [{from: {x: 640, y: 64}, to: {x: 640, y: 440}}, 8 | {from: {x: 0, y: 440}, to: {x: 0, y: 64}}], 9 | movingPaths: [{from: {x: 20, y: 440}, to: {x: 20, y: 64}}, 10 | {from: {x: 620, y: 440}, to: {x: 620, y: 64}}] 11 | }; 12 | 13 | var field3Player = { 14 | walls: [{from: {x: 99, y: 268}, to: {x: 195, y: 77}}, 15 | {from: {x: 446, y: 77}, to: {x: 542, y: 268}}, 16 | {from: {x: 446, y: 460}, to: {x: 195, y: 460}}], 17 | scoreWalls: [{from: {x: 195, y: 57}, to: {x: 446, y: 57}}, 18 | {from: {x: 542 + 14, y: 268 + 14}, to: {x: 446 + 14, y: 460 + 14}}, 19 | {from: {x: 195 - 14, y: 460 + 14}, to: {x: 99 - 14, y: 268 + 14}}], 20 | movingPaths: [{from: {x: 446, y: 77}, to: {x: 195, y: 77}}, 21 | {from: {x: 446, y: 460}, to: {x: 542, y: 268}}, 22 | {from: {x: 195, y: 460}, to: {x: 99, y: 268}}] 23 | }; 24 | 25 | var field4Player = { 26 | walls: [{from: {x: 112, y: 128}, to: {x: 189, y: 51}}, 27 | {from: {x: 450, y: 51}, to: {x: 528, y: 128}}, 28 | {from: {x: 528, y: 393}, to: {x: 450, y: 468}}, 29 | {from: {x: 189, y: 468}, to: {x: 112, y: 393}}], 30 | scoreWalls: [{from: {x: 189, y: 31}, to: {x: 450, y: 31}}, 31 | {from: {x: 548, y: 128}, to: {x: 548, y: 393}}, 32 | {from: {x: 450, y: 488}, to: {x: 189, y: 488}}, 33 | {from: {x: 92, y: 393}, to: {x: 92, y: 128}}], 34 | movingPaths: [{from: {x: 450, y: 51}, to: {x: 189, y: 51}}, 35 | {from: {x: 528, y: 393}, to: {x: 528, y: 128}}, 36 | {from: {x: 450, y: 468}, to: {x: 189, y: 468}}, 37 | {from: {x: 112, y: 393}, to: {x: 112, y: 128}}] 38 | }; 39 | 40 | // Color Codes for the paddles, although only 4 paddles are used all 6 are defined 41 | // Grey, Red, Green, Blue, Yellow, Pink 42 | var colorCodes = { 43 | grey: "#BDBDBD", 44 | red: "#FF0000", 45 | green: "#00FF00", 46 | blue: "#0000FF", 47 | yellow: "#FFFF00", 48 | pink: "#FF00FF", 49 | white: "#FFF" 50 | }; 51 | 52 | 53 | var fieldInfo = []; 54 | 55 | var build2PlayerField = function (game, wallSprite) { 56 | //create Score fpr Player 0 57 | var style0 = {font: "bold 32px Arial", fill: "#fff", boundsAlignH: "right", boundsAlignV: "middle"}; 58 | var scorePlayer0 = game.add.text(0, 0, "0", style0); 59 | scorePlayer0.setTextBounds(0, 0, (game.width / 2) - 20, 64); 60 | 61 | //Create separator 62 | var styleSeparator = {font: "bold 32px Arial", fill: "#fff", boundsAlignH: "center", boundsAlignV: "middle"}; 63 | var separator = game.add.text(0, 0, ":", styleSeparator); 64 | separator.setTextBounds((game.width / 2) - 20, 0, 40, 64); 65 | 66 | 67 | var style1 = {font: "bold 32px Arial", fill: "#fff", boundsAlignH: "left", boundsAlignV: "middle"}; 68 | var scorePlayer1 = game.add.text(0, 0, "0", style1); 69 | scorePlayer1.setTextBounds((game.width / 2) + 20, 0, (game.width / 2) - 20, 64); 70 | var walls = game.add.group(); 71 | 72 | createStaticWalls(game, wallSprite, field2Player.walls, walls); 73 | 74 | var pointWall1 = createWall(game, wallSprite, field2Player.scoreWalls[0]); 75 | var pointWall2 = createWall(game, wallSprite, field2Player.scoreWalls[1]); 76 | 77 | fieldInfo = [ 78 | {"pointWall": pointWall2, "opponentScoreText": scorePlayer1, path: field2Player.movingPaths[0]}, 79 | {"pointWall": pointWall1, "opponentScoreText": scorePlayer0, path: field2Player.movingPaths[1]} 80 | ]; 81 | 82 | return walls; 83 | }; 84 | 85 | var build3PlayerField = function (game, wallSprite) { 86 | var scorePlayer0 = createText(game, colorCodes.grey); 87 | scorePlayer0.setTextBounds(0, 0, (game.width / 2) - 20, 64); 88 | 89 | var scorePlayer1 = createText(game, colorCodes.red); 90 | scorePlayer1.setTextBounds((game.width / 2) - 20, 0, 40, 64); 91 | 92 | 93 | var scorePlayer2 = createText(game, colorCodes.green); 94 | scorePlayer2.setTextBounds((game.width / 2) + 20, 0, (game.width / 2) - 20, 64); 95 | 96 | var walls = game.add.group(); 97 | 98 | createStaticWalls(game, wallSprite, field3Player.walls, walls); 99 | 100 | var pointWall1 = createWall(game, wallSprite, field3Player.scoreWalls[0]); 101 | var pointWall2 = createWall(game, wallSprite, field3Player.scoreWalls[1]); 102 | var pointWall3 = createWall(game, wallSprite, field3Player.scoreWalls[2]); 103 | 104 | pointWall1.renderable = false; 105 | pointWall2.renderable = false; 106 | pointWall3.renderable = false; 107 | walls.add(pointWall1); 108 | walls.add(pointWall2); 109 | walls.add(pointWall3); 110 | 111 | fieldInfo = [ 112 | {"pointWall": pointWall1, "opponentScoreText": scorePlayer0, path: field3Player.movingPaths[0]}, 113 | {"pointWall": pointWall2, "opponentScoreText": scorePlayer1, path: field3Player.movingPaths[1]}, 114 | {"pointWall": pointWall3, "opponentScoreText": scorePlayer2, path: field3Player.movingPaths[2]} 115 | ]; 116 | 117 | return walls; 118 | }; 119 | 120 | var build4PlayerField = function (game, wallSprite) { 121 | resizeWindow(game, game.properties.width, game.properties.height + 40) 122 | var scorePlayer0 = createText(game, colorCodes.grey); 123 | scorePlayer0.setTextBounds(0, 0, (game.width / 4), 64); 124 | 125 | var scorePlayer1 = createText(game, colorCodes.red); 126 | scorePlayer1.setTextBounds((game.width / 4), 0, (game.width / 4), 64); 127 | 128 | 129 | var scorePlayer2 = createText(game, colorCodes.green); 130 | scorePlayer2.setTextBounds((game.width / 2), 0, (game.width / 4), 64); 131 | 132 | var scorePlayer3 = createText(game, colorCodes.blue); 133 | scorePlayer3.setTextBounds((game.width / 4) * 3, 0, (game.width / 4), 64); 134 | 135 | var walls = game.add.group(); 136 | 137 | createStaticWalls(game, wallSprite, field4Player.walls, walls); 138 | 139 | var pointWall1 = createWall(game, wallSprite, field4Player.scoreWalls[0]); 140 | var pointWall2 = createWall(game, wallSprite, field4Player.scoreWalls[1]); 141 | var pointWall3 = createWall(game, wallSprite, field4Player.scoreWalls[2]); 142 | var pointWall4 = createWall(game, wallSprite, field4Player.scoreWalls[3]); 143 | 144 | pointWall1.renderable = false; 145 | pointWall2.renderable = false; 146 | pointWall3.renderable = false; 147 | pointWall4.renderable = false; 148 | walls.add(pointWall1); 149 | walls.add(pointWall2); 150 | walls.add(pointWall3); 151 | walls.add(pointWall4); 152 | 153 | fieldInfo = [ 154 | {"pointWall": pointWall1, "opponentScoreText": scorePlayer0, path: field4Player.movingPaths[0]}, 155 | {"pointWall": pointWall2, "opponentScoreText": scorePlayer1, path: field4Player.movingPaths[1]}, 156 | {"pointWall": pointWall3, "opponentScoreText": scorePlayer2, path: field4Player.movingPaths[2]}, 157 | {"pointWall": pointWall4, "opponentScoreText": scorePlayer3, path: field4Player.movingPaths[3]} 158 | ]; 159 | 160 | return walls; 161 | }; 162 | 163 | function getPlayerInfoPack(playerNumber) { 164 | return fieldInfo[playerNumber]; 165 | } 166 | 167 | function createWall(game, sprite, positions) { 168 | return new Wall(game, sprite, positions.from, positions.to); 169 | } 170 | 171 | function createStaticWalls(game, sprite, _walls, group) { 172 | _walls.forEach(function (wall) { 173 | group.add(createWall(game, sprite, wall)); 174 | }); 175 | } 176 | function createText(game, color) { 177 | var style0 = {font: "bold 32px Arial", fill: color, boundsAlignH: "center", boundsAlignV: "middle"}; 178 | return game.add.text(0, 0, "10", style0); 179 | } 180 | function resizeWindow(game, _width, _height) { 181 | game.width = _width; 182 | game.height = _height; 183 | game.stage.width = _width; 184 | game.stage.height = _height; 185 | if (game.renderType === Phaser.WEBGL) { 186 | game.renderer.resize(_width, _height); 187 | } 188 | game.world.setBounds(0, 0, _width, _height); 189 | game.camera.setSize(_width, _height); 190 | game.camera.setBoundsToWorld(); 191 | game.scale.setShowAll(); 192 | game.scale.refresh(); 193 | } 194 | 195 | module.exports.build2PlayerField = build2PlayerField; 196 | module.exports.build3PlayerField = build3PlayerField; 197 | module.exports.build4PlayerField = build4PlayerField; 198 | module.exports.getPlayerInfoPack = getPlayerInfoPack; 199 | -------------------------------------------------------------------------------- /docu/maindocumentation/hawstyle.sty: -------------------------------------------------------------------------------- 1 | \NeedsTeXFormat{LaTeX2e} 2 | \ProvidesPackage{hawstyle}[2010/04/26 v1.0 HAW Thesis Style] 3 | 4 | \PassOptionsToPackage{absolute}{textpos} 5 | \PassOptionsToPackage{automark,autooneside}{scrpage2} 6 | \PassOptionsToPackage{colorlinks=true}{hyperref} 7 | 8 | \RequirePackage{xkeyval} 9 | \RequirePackage{ifthen} 10 | \PassOptionsToPackage{rgb}{xcolor} 11 | \RequirePackage{xcolor} 12 | \RequirePackage{calc} 13 | \RequirePackage{textpos} 14 | \RequirePackage{scrpage2} 15 | \RequirePackage{hyperref} 16 | \RequirePackage{graphicx} 17 | 18 | \newif\if@haw@printer 19 | \DeclareOption{printer}{\@haw@printertrue} 20 | \ExecuteOptions{} 21 | 22 | \ProcessOptions\relax 23 | 24 | \graphicspath{{./logo/}} 25 | 26 | %% title 27 | \def\haw@theThesisTitle{} 28 | % getter 29 | \newcommand*{\theHAWThesisTitle}{% 30 | \haw@theThesisTitle} 31 | 32 | %% subtitle 33 | \def\haw@theThesisSubTitle{} 34 | % getter 35 | \newcommand*{\theHAWThesisSubTitle}{% 36 | \haw@theThesisSubTitle} 37 | 38 | %% english title 39 | \def\haw@theThesisEnglishTitle{} 40 | % getter 41 | \newcommand*{\theHAWThesisEnglishTitle}{% 42 | \haw@theThesisEnglishTitle} 43 | 44 | %% english subtitle 45 | \def\haw@theThesisEnglishSubTitle{} 46 | % getter 47 | \newcommand*{\theHAWThesisEnglishSubTitle}{% 48 | \haw@theThesisEnglishSubTitle} 49 | 50 | %% author 51 | \def\haw@theThesisAuthor{} 52 | % getter 53 | \newcommand*{\theHAWThesisAuthor}{% 54 | \haw@theThesisAuthor} 55 | 56 | %% type 57 | \def\haw@theThesisType{} 58 | % getter 59 | \newcommand*{\theHAWThesisType}{% 60 | \haw@theThesisType} 61 | 62 | %% examination type 63 | \def\haw@theExaminationType{} 64 | % getter 65 | \newcommand*{\theHAWExaminationType}{% 66 | \haw@theExaminationType} 67 | 68 | %% degree programme 69 | \def\haw@theDegreeProgramme{} 70 | % getter 71 | \newcommand*{\theHAWDegreeProgramme}{% 72 | \haw@theDegreeProgramme} 73 | 74 | %% experts 75 | \def\haw@theThesisExperts{} 76 | % getter 77 | \newcommand*{\theHAWExperts}{% 78 | \haw@theThesisExperts} 79 | 80 | %% subtitle delimiter, defaults to . (dot) 81 | \def\haw@theSubTitleDelimiter{} 82 | % getter 83 | \newcommand*{\theHAWSubTitleDelimiter}{% 84 | \haw@theSubTitleDelimiter} 85 | 86 | %% date of release, default to \today 87 | \def\haw@theReleaseDate{} 88 | % getter 89 | \newcommand*{\theHAWReleaseDate}{% 90 | \haw@theReleaseDate} 91 | 92 | \def\haw@FullTitle{} 93 | \newcommand{\haw@createFullTitle}[2]{% 94 | \ifthenelse{\equal{#2}{}}{ 95 | \def\haw@FullTitle{#1} 96 | }{ 97 | \def\haw@FullTitle{#1\theHAWSubTitleDelimiter\ #2} 98 | }} 99 | 100 | \define@key[haw]{prop}{Title}{\def\haw@theThesisTitle{#1}} 101 | \define@key[haw]{prop}{SubTitle}[]{\def\haw@theThesisSubTitle{#1}} 102 | \define@key[haw]{prop}{Author}{\def\haw@theThesisAuthor{#1}} 103 | \define@key[haw]{prop}{EnglishTitle}{\def\haw@theThesisEnglishTitle{#1}} 104 | \define@key[haw]{prop}{EnglishSubTitle}[]{\def\haw@theThesisEnglishSubTitle{#1}} 105 | \define@key[haw]{prop}{ThesisType}{\def\haw@theThesisType{#1}} 106 | \define@key[haw]{prop}{ExaminationType}{\def\haw@theExaminationType{#1}} 107 | \define@key[haw]{prop}{DegreeProgramme}{\def\haw@theDegreeProgramme{#1}} 108 | \define@key[haw]{prop}{ThesisExperts}{\def\haw@theThesisExperts{#1}} 109 | \define@key[haw]{prop}{SubTitleDelimiter}[.]{\def\haw@theSubTitleDelimiter{#1}} 110 | \define@key[haw]{prop}{ReleaseDate}[\today]{\def\haw@theReleaseDate{#1}} 111 | 112 | %% preset values 113 | \presetkeys[haw]{prop}{ReleaseDate}{} 114 | 115 | \newcommand{\HAWThesisProperties}[1]{% 116 | \setkeys[haw]{prop}{#1} 117 | \ifthenelse{\equal{\theHAWThesisTitle}{}}{ 118 | \PackageWarningNoLine{hawstyle}{% 119 | No thesis title set, add something like:\MessageBreak 120 | `\string\HAWThesisProperties[Title={Your Title}'\MessageBreak 121 | `\string EnglishTitle={Your English Title}'\MessageBreak 122 | `\string Author={Your Name}'\MessageBreak 123 | to your document.}} 124 | \ifthenelse{\equal{\theHAWThesisAuthor}{}}{ 125 | \PackageWarningNoLine{hawstyle}{% 126 | No thesis author set, add something like:\MessageBreak 127 | `\string\HAWThesisProperties[Title={Your Title}'\MessageBreak 128 | `\string EnglishTitle={Your English Title}'\MessageBreak 129 | `\string Author={Your Name}'\MessageBreak 130 | to your document.}} 131 | \ifthenelse{\equal{\theHAWThesisEnglishTitle}{}}{ 132 | \PackageWarningNoLine{hawstyle}{% 133 | No thesis author set, add something like:\MessageBreak 134 | `\string\HAWThesisProperties[Title={Your Title}'\MessageBreak 135 | `\string EnglishTitle={Your English Title}'\MessageBreak 136 | `\string Author={Your Name}'\MessageBreak 137 | to your document.}} 138 | 139 | \haw@createFullTitle{\haw@theThesisTitle}{\haw@theThesisSubTitle} 140 | \hypersetup{pdfsubject={\haw@FullTitle} 141 | ,pdftitle={\haw@FullTitle} 142 | ,pdfauthor={\haw@theThesisAuthor} 143 | }} 144 | 145 | \renewcommand*{\maketitle}{ 146 | \begin{titlepage}% 147 | \haw@Cover 148 | \haw@Title 149 | \end{titlepage} 150 | } 151 | 152 | \newcommand*{\haw@Cover}{% 153 | \begingroup 154 | \thispagestyle{empty}% 155 | \enlargethispage{\footskip}% 156 | \setlength{\parindent}{0em} % remove indent 157 | \sffamily % the title page in sans serif 158 | 159 | \if@haw@printer 160 | \colorlet{HAWBannerColor}{white} 161 | \else 162 | \definecolor{HAWBannerColor}{rgb}{0.00,0.78,0.54} 163 | \fi 164 | 165 | \ifthenelse{\equal{\theHAWThesisTitle}{}}{ 166 | \PackageWarningNoLine{hawstyle}{% 167 | No thesis title set, add something like:\MessageBreak 168 | `\string\HAWThesisProperties[Title={Your Title}'\MessageBreak 169 | `\string EnglishTitle={Your English Title}'\MessageBreak 170 | `\string Author={Your Name}'\MessageBreak 171 | to your document.}} 172 | \ifthenelse{\equal{\theHAWThesisAuthor}{}}{ 173 | \PackageWarningNoLine{hawstyle}{% 174 | No author set, add something like:\MessageBreak 175 | `\string\HAWThesisProperties[Title={Your Title}'\MessageBreak 176 | `\string EnglishTitle={Your English Title}'\MessageBreak 177 | `\string Author={Your Name}'\MessageBreak 178 | to your document.}} 179 | 180 | %% use HAW for online version, nothing if the file is to be printed 181 | \begin{textblock*}{2cm}(10cm,2cm) 182 | \begin{minipage}[r]{\textwidth}% 183 | \if@haw@printer % hide logo if printer is true 184 | \relax 185 | \else 186 | \includegraphics[width=10cm]{HAW_logo}% 187 | \fi 188 | \end{minipage}% 189 | \end{textblock*} 190 | 191 | %% color bar 192 | \begin{textblock*}{10cm}(0cm,9cm) 193 | \colorbox{HAWBannerColor}{% 194 | %% use a minipage that is the size of the colored banner 195 | %% center the minipage 196 | \begin{minipage}[l][10cm][c]{\paperwidth}% 197 | %% put the box on the right side 198 | \hspace*{0.25\textwidth} 199 | \parbox[t]{0.7\textwidth}{% 200 | {\centering\bfseries\Huge\theHAWThesisType 201 | 202 | \bigskip 203 | \Large\theHAWThesisAuthor 204 | 205 | \vspace{2em} 206 | \theHAWThesisTitle 207 | 208 | \bigskip 209 | \large\theHAWThesisSubTitle\par 210 | }}% 211 | \end{minipage}% 212 | } 213 | \end{textblock*} 214 | %% HAW information 215 | \newlength{\haw@xpos}\newlength{\haw@fw} 216 | \setlength{\haw@xpos}{1.5cm} 217 | \setlength{\haw@fw}{\paperwidth} 218 | \addtolength{\haw@fw}{-2\haw@xpos} 219 | \begin{textblock*}{2cm}(\haw@xpos,0.85\paperheight) 220 | \begin{minipage}[c]{\haw@fw} 221 | \centering\newlength{\haw@tw} 222 | \settowidth{\haw@tw}{Fakult\"at Technik und Informatik} 223 | \parbox[t]{\haw@tw}{% 224 | \normalfont\itshape% 225 | Fakultät Technik und Informatik\\ 226 | Studiendepartment Informatik 227 | }\hfill 228 | \settowidth{\haw@tw}{Faculty of Engineering and Computer Science} 229 | \parbox[t]{\haw@tw}{% 230 | \normalfont\itshape% 231 | Faculty of Engineering and Computer Science\\ 232 | Department of Computer Science 233 | } 234 | \end{minipage} 235 | \end{textblock*} 236 | \null\newpage 237 | \endgroup 238 | } 239 | 240 | % internal function 241 | \newcommand*{\haw@Title}{ 242 | \begingroup 243 | \enlargethispage{\footskip} 244 | \thispagestyle{empty} 245 | \setlength{\parindent}{0em} %% remove indent 246 | {\centering\Large\theHAWThesisAuthor\\ 247 | \bigskip 248 | \bfseries\theHAWThesisTitle\\ 249 | \ifthenelse{\equal{\theHAWThesisSubTitle}{}}{}{ 250 | \medskip\large\theHAWThesisSubTitle\\}% 251 | } 252 | \vfill 253 | \ifthenelse{\NOT\equal{\theHAWThesisType}{} \AND 254 | \NOT\equal{\theHAWExaminationType}{} \AND 255 | \NOT\equal{\theHAWDegreeProgramme}{}}{ 256 | \theHAWThesisType\ eingereicht im Rahmen der \theHAWExaminationType\\ \par 257 | im Studiengang \theHAWDegreeProgramme\\ 258 | am Department Informatik\\ 259 | der Fakultät Technik und Informatik\\ 260 | der Hochschule für Angewandte Wissenschaften Hamburg\par 261 | \bigskip} 262 | 263 | \@tempcnta=0 264 | \def\and{\strut\\ 265 | \advance\@tempcnta by 1 266 | \ifcase\@tempcnta Betreuender Prüfer:\ \ignorespaces 267 | \or Zweitgutachter:\ \ignorespaces 268 | \fi 269 | }% 270 | \ifthenelse{\not\equal{\theHAWExperts}{}}{% 271 | Betreuender Prüfer:\ \ignorespaces\theHAWExperts\par \bigskip} \par 272 | Eingereicht am: \theHAWReleaseDate 273 | \endgroup 274 | } 275 | 276 | %% abstract page 277 | % Parameter 1: keywords german 278 | % Parameter 2: abstract german 279 | % Parameter 3: keywords english 280 | % Parameter 4: abstract english 281 | \newcommand{\HAWAbstractPage}[4]{ 282 | \begingroup 283 | % \setlength{\parindent}{0em} 284 | %% german version 285 | \thispagestyle{empty} 286 | \ifthenelse{\equal{\theHAWThesisSubTitle}{}}{ 287 | \ifthenelse{\equal{\theHAWThesisEnglishSubTitle}{}}{ 288 | }{%% Only English subtitle is defined. 289 | \PackageWarningNoLine{hawstyle}{% 290 | Only English subtitle has been set} 291 | } 292 | }{ 293 | \ifthenelse{\equal{\theHAWThesisEnglishSubTitle}{}}{ 294 | %% Only German subtitle is defined. 295 | \PackageWarningNoLine{hawstyle}{% 296 | Only German subtitle has been set} 297 | }{ 298 | } 299 | } 300 | \@ifundefined{otherlanguage}{% 301 | \PackageWarningNoLine{hawstyle}{% 302 | You should use babel package.\MessageBreak 303 | Simply add `\string\usepackage[ngerman,]{babel}' 304 | to the\MessageBreak 305 | document preamble}% 306 | }{% 307 | \begin{otherlanguage}{ngerman} 308 | } 309 | {\setlength{\parindent}{0em}\bfseries\theHAWThesisAuthor}\\ 310 | \medskip 311 | 312 | {\setlength{\parindent}{0em}\bfseries Thema der Arbeit}\\ 313 | \theHAWThesisTitle% 314 | \ifthenelse{\equal{\theHAWThesisSubTitle}{}}{}{% 315 | \theHAWSubTitleDelimiter\ \theHAWThesisSubTitle} 316 | \medskip 317 | 318 | {\setlength{\parindent}{0em}\bfseries Stichworte}\\ 319 | #1 320 | \medskip 321 | 322 | {\setlength{\parindent}{0em}\bfseries Kurzzusammenfassung}\\ 323 | #2 324 | \@ifundefined{otherlanguage}{}{\end{otherlanguage}} 325 | 326 | %% english version 327 | \thispagestyle{empty} 328 | \@ifundefined{otherlanguage}{% 329 | \PackageWarningNoLine{hawstyle}{% 330 | You should use babel package.\MessageBreak 331 | Simply add `\string\usepackage[english,]{babel}' 332 | to the\MessageBreak 333 | document preamble}% 334 | }{% 335 | \begin{otherlanguage}{ngerman} 336 | } 337 | %% set the author name in German language (allows umlauts with "a) 338 | {\setlength{\parindent}{0em}\bfseries\theHAWThesisAuthor}\\ 339 | \medskip 340 | \@ifundefined{otherlanguage}{}{\end{otherlanguage}} 341 | 342 | \@ifundefined{otherlanguage}{}{\begin{otherlanguage}{english}} 343 | {\setlength{\parindent}{0em}\bfseries Title of the paper}\\ 344 | \theHAWThesisEnglishTitle% 345 | \ifthenelse{\equal{\theHAWThesisEnglishSubTitle}{}}{}{% 346 | \theHAWSubTitleDelimiter\ \theHAWThesisEnglishSubTitle} 347 | \medskip 348 | 349 | {\setlength{\parindent}{0em}\bfseries Keywords}\\ 350 | #3 351 | \medskip 352 | 353 | {\setlength{\parindent}{0em}\bfseries Abstract}\\ 354 | #4 355 | \@ifundefined{otherlanguage}{}{\end{otherlanguage}} 356 | \endgroup 357 | \pagebreak 358 | } 359 | 360 | \AtBeginDocument{ 361 | % redefine PDF info fields 362 | \ifpdfoutput{ 363 | \def\haw@title{} 364 | \ifthenelse{\equal{\theHAWThesisSubTitle}{}}{ 365 | \def\haw@title{\theHAWThesisTitle} 366 | }{ 367 | \def\haw@title{\theHAWThesisTitle\theHAWSubTitleDelimiter\ % 368 | \theHAWThesisSubTitle} 369 | } 370 | 371 | \pdfcompresslevel=9 372 | \DeclareGraphicsExtensions{.png,.pdf,.jpg} 373 | \DeclareGraphicsRule{*}{mps}{*}{} 374 | 375 | \if@haw@printer 376 | \colorlet{LinkColor}{black} 377 | \colorlet{CiteColor}{black} 378 | \else 379 | \colorlet{LinkColor}{blue} 380 | \colorlet{CiteColor}{red} 381 | \fi 382 | \hypersetup{pdfsubject={\haw@title} 383 | ,pdftitle={\haw@title} 384 | ,pdfauthor={\theHAWThesisAuthor} 385 | ,linkcolor=CiteColor 386 | ,citecolor=CiteColor 387 | ,urlcolor=LinkColor 388 | ,filecolor=LinkColor} 389 | }{ 390 | \RequirePackage{nameref} 391 | \DeclareGraphicsExtensions{.eps,.ps} 392 | } 393 | 394 | \pagestyle{scrheadings} 395 | \clearscrheadfoot 396 | \ihead{\headmark} 397 | \chead{} 398 | \ohead{} 399 | \ifoot{} 400 | \cfoot[\pagemark]{\pagemark} 401 | \ofoot{} 402 | } 403 | 404 | %% conditional test 405 | %% executes arg 1 if printer option has been passed to package 406 | %% else arg 2 is executed 407 | \newcommand{\HAWifprinter}[2]{% 408 | \if@haw@printer 409 | \expandafter#1\relax 410 | \else 411 | \expandafter#2\relax 412 | \fi 413 | } 414 | 415 | \newcommand*{\detailref}[1]{\ref{#1} (S.~\pageref{#1})} 416 | 417 | \def\haw@defaultAsurency{% 418 | Hiermit versichere ich, dass ich die vorliegende 419 | Arbeit ohne fremde Hilfe selb\-st\"an\-dig verfasst und nur die 420 | angegebenen Hilfsmittel benutzt habe. 421 | } 422 | 423 | \newcommand{\HAWasurency}[1][\haw@defaultAsurency]{% 424 | \thispagestyle{empty} 425 | 426 | \noindent\emph{#1} 427 | 428 | \vspace{2cm}\noindent Hamburg, 429 | \theHAWReleaseDate\quad$\overline{\textsf{\theHAWThesisAuthor}\hspace{6cm}}$ 430 | } 431 | -------------------------------------------------------------------------------- /src/own_modules/WebRTC-COMM.js: -------------------------------------------------------------------------------- 1 | module.exports.Client = Client; 2 | module.exports.Server = Server; 3 | module.exports.ServerRooms = rooms; 4 | module.exports.getID = getUUID; 5 | 6 | 7 | var rtcconfig = { 8 | 'iceServers': [ 9 | //{ 'urls': 'stun:stun.l.google.com:19302' }, 10 | //{ 'urls': 'stun:stun1.l.google.com:19305' }, 11 | //{ 'urls': 'stun:stun2.l.google.com:19305' }, 12 | //{ 'urls': 'stun:stun3.l.google.com:19305' }, 13 | //{ 'urls': 'stun:stun4.l.google.com:19305' }, 14 | { 'urls': 'stun:stun.schlung.de' } 15 | ] 16 | }; 17 | 18 | var dataChannelOptions = { 19 | outOfOrderAllowed: true, 20 | maxRetransmitNum: 0 21 | }; 22 | 23 | function Client(iosocket, config, room, signallingCallback) { 24 | 25 | // @TODO try reproduce socket.io timing problems by removing this. 26 | sleep(100); 27 | 28 | // Scope helper 29 | var self = this; 30 | 31 | self.iosocket = iosocket; 32 | 33 | self.room = room; 34 | 35 | /* 36 | * Overload RTCPeerConnection and RTCSessionDescription 37 | * to support Browsers that have not prefixed this. 38 | */ 39 | self.peerConnection = window.RTCPeerConnection || 40 | window.mozRTCPeerConnection || 41 | window.webkitRTCPeerConnection || 42 | window.msRTCPeerConnection; 43 | 44 | self.sessionDesc = window.RTCSessionDescription || 45 | window.mozRTCSessionDescription || 46 | window.webkitRTCSessionDescription || 47 | window.msRTCSessionDescription; 48 | 49 | self.dataChannel = window.RTCDataChannel || 50 | window.DataChannel; 51 | 52 | 53 | 54 | // Own ID 55 | self.id = self.iosocket.id; 56 | 57 | // Number of connected Peers (WebRTC) 58 | self.connectioncount = 0; 59 | 60 | // Object containing all connections 61 | self.rtcPeerConnections = {}; 62 | 63 | // Callback when reveiving a message on the DataChannel 64 | self.messageCallback = null; 65 | 66 | // Callback when a new Connection has been established 67 | self.newConnectionCallback = null; 68 | 69 | // Callback when a Peer has been disconnected 70 | self.disconnectCallback = null; 71 | 72 | 73 | // Set Client Config 74 | if( config ) { 75 | self.mode = config.hasOwnProperty('mode') ? config.mode : 'single'; 76 | self.maxpeers = config.hasOwnProperty('maxpeers') ? config.mode : 1; 77 | } else { 78 | self.mode = 'single'; 79 | self.maxpeers = 1; 80 | } 81 | 82 | 83 | // Check Client Config 84 | if( self.mode === 'single' ) { 85 | 86 | // Mode is Single 87 | if( self.maxpeers != 1 ) { 88 | 89 | // ERROR: maxpeers in single has to be 1 90 | console.log("ERROR: maxpeers in single has to be 1"); 91 | } 92 | } else if( self.mode === 'multi' ) { 93 | 94 | // Mode is Multi 95 | if( self.maxpeers < 2 ) { 96 | 97 | // ERROR: maxpeers in multi has to be bigger than 1 98 | console.log("ERROR: maxpeers in multi has to be bigger than 1"); 99 | } 100 | } else if( clientConfig.mode.indexOf(self.mode) === -1 ) { 101 | 102 | // ERROR: unknown mode specified 103 | console.log("ERROR: unknown mode specified"); 104 | } else { 105 | 106 | // ERROR: unknown error 107 | console.log("ERROR: unknown error"); 108 | } 109 | 110 | // @TODO replace by Logging 111 | console.log("I AM: " + self.iosocket.id); 112 | 113 | 114 | self.iosocket.on('signalling_message', function(data) { 115 | console.log("new signal => " + JSON.stringify(data)); 116 | 117 | // Start Setting up connection after at least one other Peer 118 | // is connected to the Server. 119 | // @IMPORTANT We receive only Signallin messages from OTHER Peers 120 | // newConnection(); 121 | 122 | if( data.type === 'hereandready' ) { 123 | 124 | // New User Connected to game 125 | console.log("New User connected."); 126 | 127 | var newUserID = data.from; 128 | 129 | newPeerToPeerConnection(newUserID); 130 | } else if( data.type === 'peer_disconnect' && self.mode === 'multi' ) { 131 | 132 | // A Peer has been disconnected 133 | var disconnectedPeerId = data.from; 134 | peerDisconnected(disconnectedPeerId); 135 | } else if( data.type === 'host_disconnect' && self.mode === 'single' ) { 136 | 137 | // Connection to host peer has been lost 138 | var disconnectedPeerId = data.from; 139 | hostDisconnected(disconnectedPeerId); 140 | } else if( data.type === 'candidate' ) { 141 | 142 | // Received a ICE Candidate 143 | var fromPeer = data.from; 144 | var message = JSON.parse(data.message); 145 | var connobj; 146 | 147 | if( self.mode === 'single' ) { 148 | 149 | if( !self.rtcPeerConnections[0] ) { 150 | newPeerToPeerConnection(fromPeer); 151 | } 152 | 153 | connobj = self.rtcPeerConnections[0]; 154 | } else if( self.mode === 'multi' ) { 155 | connobj = self.rtcPeerConnections[fromPeer]; 156 | } 157 | 158 | handleIceCandidate(connobj, message); 159 | } else { 160 | 161 | // Connection Controlling Signal 162 | var message = JSON.parse(data.message); 163 | 164 | if( message.sdp ) { 165 | 166 | // SDP 167 | receivedSessionInfo(message, data.from); 168 | } else { 169 | 170 | // ERROR: Unknown Singal 171 | // @TODO handle Errors properly, use log?! 172 | console.log("Unknown Signal: " + JSON.stringify(message)); 173 | } 174 | } 175 | }); 176 | 177 | // Send initial Signal to Server 178 | // If Mode id Multi, this signal will be received by no one 179 | // @TODO refactor this! 180 | // If Mode is Single, the signal will be redirected to the Multi-Peer 181 | self.iosocket.emit('signal', {"type":"hereandready", "mode":self.mode, "room":room}); 182 | 183 | function peerDisconnected(peerid) { 184 | if( !self.rtcPeerConnections[peerid] ) return; 185 | 186 | var connobj = self.rtcPeerConnections[peerid]; 187 | 188 | connobj.connection.close(); 189 | 190 | delete self.rtcPeerConnections[peerid]; 191 | 192 | if( self.disconnectCallback ) { 193 | self.disconnectCallback(peerid); 194 | } 195 | }; 196 | 197 | function hostDisconnected(peerid) { 198 | if( !self.rtcPeerConnections[0] ) return; 199 | 200 | var connobj = self.rtcPeerConnections[0]; 201 | 202 | connobj.connection.close(); 203 | 204 | delete self.rtcPeerConnections[0]; 205 | 206 | if( self.disconnectCallback ) { 207 | self.disconnectCallback(peerid); 208 | } 209 | }; 210 | 211 | function receivedSessionInfo(sessioninfo, peerid) { 212 | return new Promise(function(resolve, reject) { 213 | var connobj; 214 | 215 | if( self.mode === 'single' ) { 216 | 217 | // Mode is Single, we need to init a new Peer to Peer connection on our side 218 | // Then send our Session info back to the Host 219 | if( !self.rtcPeerConnections[0] ) { 220 | newPeerToPeerConnection(peerid); 221 | } 222 | 223 | connobj = self.rtcPeerConnections[0]; 224 | 225 | single_ICECandidatesReady(connobj, sessioninfo); 226 | } else if( self.mode === 'multi' ) { 227 | connobj = self.rtcPeerConnections[peerid]; 228 | 229 | connobj.connection.setRemoteDescription(new self.sessionDesc(sessioninfo.sdp)) 230 | .then(function() { 231 | console.log("Setting Remote Description"); 232 | }) 233 | .catch(function(reason) { 234 | console.log("ERROR: " + reason); 235 | }); 236 | } else { 237 | reject(Error("Unknown Mode specified")); 238 | } 239 | }); 240 | } 241 | 242 | function multi_initDataChannel(connobj) { 243 | connobj.datachannel = connobj.connection.createDataChannel("gameUpdates", dataChannelOptions); 244 | 245 | connobj.datachannel.onopen = function(eventdata) { 246 | console.log("Datachannel now open"); 247 | }; 248 | 249 | connobj.datachannel.onclose = function(eventdata) { 250 | console.log("Dartachannel now closed"); 251 | }; 252 | 253 | connobj.datachannel.onmessage = function(message) { 254 | console.log("Datachannel: " + message.data); 255 | var data = JSON.parse(message.data); 256 | if( self.messageCallback ) { 257 | self.messageCallback(data.type, data.from, data.message); 258 | } 259 | }; 260 | } 261 | 262 | function single_initDataChannel(connobj) { 263 | console.log("Waiting for DataChannel"); 264 | 265 | connobj.connection.ondatachannel = function(data) { 266 | console.log("Received DataChannel"); 267 | 268 | connobj.datachannel = data.channel; 269 | 270 | connobj.datachannel.onopen = function(eventdata) { 271 | console.log("Datachannel now open"); 272 | }; 273 | 274 | connobj.datachannel.onclose = function(eventdata) { 275 | console.log("Dartachannel now closed"); 276 | }; 277 | 278 | connobj.datachannel.onmessage = function(message) { 279 | console.log("Datachannel: " + message.data); 280 | var data = JSON.parse(message.data); 281 | if( self.messageCallback ) { 282 | self.messageCallback(data.type, data.from, data.message); 283 | } 284 | }; 285 | }; 286 | } 287 | 288 | function single_ICECandidatesReady(connobj, sessioninfo) { 289 | console.log("Setting Remote Description"); 290 | connobj.connection.setRemoteDescription(new self.sessionDesc(sessioninfo.sdp)) 291 | .then(function() { 292 | console.log("Creating Answer"); 293 | return connobj.connection.createAnswer(); 294 | }) 295 | .then(function(answer) { 296 | console.log("Setting Local Description"); 297 | return connobj.connection.setLocalDescription(answer); 298 | }) 299 | .then(function() { 300 | gotAllICECandidates(connobj); 301 | }) 302 | .catch(function(reason) { 303 | console.log("ERROR: " + reason); 304 | }); 305 | } 306 | 307 | function multi_ICECandidatesReady(connobj) { 308 | console.log("Creating Offer"); 309 | connobj.connection.createOffer() 310 | .then(function(offer) { 311 | console.log("Setting Local Description"); 312 | return connobj.connection.setLocalDescription(offer); 313 | }) 314 | .then(function() { 315 | gotAllICECandidates(connobj); 316 | }) 317 | .catch(function(reason) { 318 | console.log("ERROR: " + reason); 319 | }); 320 | } 321 | 322 | function gotAllICECandidates(connobj) { 323 | console.log("Sending Session Informations"); 324 | sendSignal({ 325 | type: "SDP", 326 | to: connobj.remotepeerid, 327 | message: JSON.stringify({ 328 | sdp: connobj.connection.localDescription 329 | }), 330 | room: self.room 331 | }); 332 | } 333 | 334 | function sendSignal(signalContent) { 335 | // Sending Signal to Signalling Server 336 | self.iosocket.emit("signal", signalContent); 337 | } 338 | 339 | function sendCandidate(connobj, candidate) { 340 | sendSignal({ 341 | type: "candidate", 342 | to: connobj.remotepeerid, 343 | message: JSON.stringify(candidate), 344 | room: self.room 345 | }); 346 | } 347 | 348 | function handleIceCandidate(connobj, candidate) { 349 | connobj.connection.addIceCandidate(new RTCIceCandidate(candidate)); 350 | } 351 | 352 | function newPeerToPeerConnection(peerid) { 353 | if( self.mode === 'single' && self.connectioncount == 0 ) { 354 | 355 | // Mode is Single and no active Connections 356 | self.connectioncount++; 357 | 358 | console.log("Mode is Single"); 359 | 360 | self.rtcPeerConnections[0] = { 361 | connection: null, 362 | datachannel: null, 363 | sdpdone: false, 364 | remotepeerid: peerid 365 | }; 366 | 367 | var connobj = self.rtcPeerConnections[0]; 368 | 369 | connobj.connection = new self.peerConnection(rtcconfig); 370 | 371 | single_initDataChannel(connobj); 372 | 373 | connobj.connection.onicecandidate = function(data) { 374 | if( data.candidate ) { 375 | console.log("Got ICE Candidate: " + JSON.stringify(data.candidate)); 376 | sendCandidate(connobj, data.candidate); 377 | } else { 378 | // single_ICECandidatesReady(); 379 | // gotAllICECandidates(connobj);); 380 | } 381 | }; 382 | } else if( self.mode === "multi" ) { 383 | 384 | console.log("Mode is Multi"); 385 | 386 | self.connectioncount++; 387 | 388 | self.rtcPeerConnections[peerid] = { 389 | connection: null, 390 | datachannel: null, 391 | sdpdone: false, 392 | remotepeerid: peerid 393 | }; 394 | 395 | var connobj = self.rtcPeerConnections[peerid]; 396 | 397 | connobj.connection = new self.peerConnection(rtcconfig); 398 | 399 | multi_initDataChannel(connobj); 400 | 401 | connobj.connection.onicecandidate = function(data) { 402 | if( data.candidate ) { 403 | console.log("Got ICE Candidate: " + JSON.stringify(data.candidate)); 404 | sendCandidate(connobj, data.candidate); 405 | } else { 406 | // multi_ICECandidatesReady(connobj); 407 | // gotAllICECandidates(connobj); 408 | 409 | } 410 | }; 411 | } 412 | 413 | // Setup complete, EventListener are same for Single and Multi 414 | 415 | connobj.connection.onconnectionstatechange = function(data) { 416 | var state = connobj.connection.connectionState; 417 | 418 | console.log("Connectionstate changed <" + state + ">"); 419 | 420 | connectionStateChange(connobj, state); 421 | }; 422 | 423 | connobj.connection.oniceconnectionstatechange = function(data) { 424 | var state = connobj.connection.iceConnectionState; 425 | 426 | console.log("ICEConnectionstate changed <" + state + ">"); 427 | 428 | connectionStateChange(connobj, state); 429 | }; 430 | 431 | if( self.mode === 'multi' ) { 432 | multi_ICECandidatesReady(connobj); 433 | // gotAllICECandidates(connobj); 434 | } else { 435 | // single_ICECandidatesReady(connobj); 436 | // gotAllICECandidates(connobj); 437 | } 438 | 439 | } 440 | 441 | function connectionStateChange(connobj, state) { 442 | switch( state ) { 443 | case "connected": 444 | console.log("Successfully connected via WebRTC!"); 445 | peerconnected(connobj.remotepeerid); 446 | break; 447 | 448 | case "disconnected": 449 | console.log("Disconnected from other Peer!"); 450 | peerdisconnected(connobj.remotepeerid); 451 | break; 452 | 453 | case "failed": 454 | console.log("Could not establish a WebRTC connection!"); 455 | break; 456 | } 457 | } 458 | 459 | function peerconnected(peerid) { 460 | // A new Peer has been successfully connected 461 | 462 | if( self.newConnectionCallback ) { 463 | self.newConnectionCallback(peerid); 464 | } 465 | } 466 | 467 | function peerdisconnected(peerid) { 468 | // A Peer connection has been disconnected 469 | 470 | if( self.disconnectCallback ) { 471 | self.disconnectCallback(peerid); 472 | } 473 | } 474 | }; 475 | 476 | Client.prototype.setMessageCallback = function(newCallback) { 477 | this.messageCallback = newCallback; 478 | }; 479 | 480 | Client.prototype.setNewConnectionCallback = function(newCallback) { 481 | this.newConnectionCallback = newCallback; 482 | }; 483 | 484 | Client.prototype.setDisconnectCallback = function(newCallback) { 485 | this.disconnectCallback = newCallback; 486 | }; 487 | 488 | Client.prototype.sendMessage = function(msgType, data) { 489 | var self = this; 490 | 491 | if( this.mode === 'single' ) { 492 | this.rtcPeerConnections[0].datachannel.send(JSON.stringify( { "type":msgType, "from":self.id, "message":data } )); 493 | } else { 494 | this.peers.forEach(function(conn) { 495 | self.rtcPeerConnections[""+conn].datachannel.send(JSON.stringify( { "type":msgType, "from":self.id, "message":data } )); 496 | }); 497 | } 498 | }; 499 | 500 | Client.prototype.changeRoom = function(newroom) { 501 | // @TODO WIP 502 | }; 503 | 504 | Client.prototype.setSignallingCallback = function(newCallback) { 505 | // @TODO WIP 506 | }; 507 | 508 | 509 | 510 | 511 | var rooms = {}; 512 | 513 | function Server(iosocket, config) { 514 | // Scope helper 515 | var self = this; 516 | 517 | // Socket from Socket.io 518 | self.iosocket = iosocket; 519 | 520 | // room 521 | self.room = ""; 522 | 523 | // Mode 524 | self.mode = ""; 525 | 526 | self.iosocket.on('disconnect', function(data) { 527 | console.log("Lost Connection to a Socket..."); 528 | 529 | sendDisconnectSignal(); 530 | leaveRoom(); 531 | }); 532 | 533 | self.iosocket.on('signal', function(req) { 534 | console.log(self.iosocket.id + " => " + JSON.stringify(req)); 535 | 536 | if( req.type === "hereandready" ) { 537 | if( !newPeer(req.room, req.mode) ) { 538 | 539 | // ERROR 540 | return; 541 | } 542 | } 543 | 544 | if( req.hasOwnProperty('to') ) { 545 | 546 | // @TODO replace direct Array indexing by Object-Named indexing for direct user access 547 | rooms[req.room].users.forEach(function(user) { 548 | if( user.id === req.to ) { 549 | 550 | // Send Signalling Message to single Peer 551 | user.emit('signalling_message', { 552 | from: self.iosocket.id, 553 | type: req.type, 554 | message: req.message 555 | }); 556 | } 557 | }); 558 | } else { 559 | 560 | // No further defined target, send to Multi-Peer 561 | if( self.iosocket.id !== rooms[req.room].users[0].id ) { 562 | rooms[req.room].users[0].emit('signalling_message', { 563 | from: self.iosocket.id, 564 | type: req.type, 565 | message: req.message 566 | }); 567 | } 568 | } 569 | }); 570 | 571 | function newPeer(room, mode){ 572 | if( rooms.hasOwnProperty(room) ) { 573 | 574 | // Room exists, check if Single-Peer 575 | if( mode === 'single' ) { 576 | 577 | // All OK, connect to Room 578 | joinRoom(room); 579 | self.mode = 'single'; 580 | } else { 581 | 582 | // Multiple Multi-Peers currently not supported in one room 583 | // @TODO ERROR 584 | console.log("Multi-Peer tried to join room with another Multi-Peer"); 585 | return false; 586 | } 587 | } else { 588 | 589 | // Room doesnt exist 590 | if( mode === 'single' ) { 591 | 592 | // Singe-Peers can't create Rooms 593 | // @TODO ERROR 594 | console.log("Single-Peer tried to create Room."); 595 | return false; 596 | } else { 597 | 598 | // All OK, create Room 599 | createRoom(room); 600 | joinRoom(room); 601 | self.mode = 'multi'; 602 | } 603 | } 604 | 605 | return true; 606 | }; 607 | 608 | function createRoom(roomname) { 609 | 610 | console.log("New Room created: " + roomname); 611 | 612 | rooms[roomname] = { 613 | users: [], 614 | userCount: 0, 615 | state: 'idle' 616 | }; 617 | }; 618 | 619 | function joinRoom(room) { 620 | 621 | console.log("[" + self.iosocket.id + "] joined Room " + room); 622 | 623 | self.iosocket.join(room); 624 | rooms[room].users.push(self.iosocket); 625 | 626 | self.room = room; 627 | }; 628 | 629 | function leaveRoom() { 630 | if( self.room === "" ) return; 631 | if( !rooms[self.room] ) return; 632 | 633 | var index = rooms[self.room].users.indexOf(self.iosocket); 634 | 635 | if( index == 0 ) { 636 | 637 | // Host has disconnected from Room 638 | // Delete Room 639 | console.log("Deleted Room " + self.room + " because room Host disconnected."); 640 | 641 | rooms[self.room].users.forEach(function(user) { 642 | if( user.id !== self.iosocket.id ) { 643 | 644 | // Let just others leave 645 | // Self left already because of disconnect 646 | user.leave(self.room); 647 | } 648 | }); 649 | 650 | delete rooms[self.room]; 651 | } else if( index > -1 ) { 652 | 653 | // Remove Socket from room 654 | console.log("[" + self.iosocket.id + "] has been removed from Room " + self.room); 655 | rooms[self.room].users.splice(index, 1); 656 | } else { 657 | 658 | // @TODO handle not user in room 659 | console.log("User index in room is < -1."); 660 | } 661 | }; 662 | 663 | function sendDisconnectSignal() { 664 | if( self.room === "" ) return; 665 | if( !rooms[self.room] ) return; 666 | 667 | if( self.mode === 'single' ) { 668 | 669 | // Emit Disconnect Signal to room host 670 | rooms[self.room].users[0].emit('signalling_message', { 671 | from: self.iosocket.id, 672 | type: 'peer_disconnect' 673 | }); 674 | } else if( self.mode === 'multi' ) { 675 | 676 | // Emit Disconnect Signal to all 677 | rooms[self.room].users.forEach(function(user) { 678 | user.emit('signalling_message', { 679 | from: self.iosocket.id, 680 | type: 'host_disconnect' 681 | }); 682 | }); 683 | } else { 684 | // @TODO Handle unknwon Mode 685 | } 686 | } 687 | } 688 | 689 | Server.prototype.sendStatus = function(room) { 690 | // @TODO WIP ? 691 | } 692 | 693 | 694 | // Dirty Utilities 695 | function sleep(milliseconds) { 696 | var start = new Date().getTime(); 697 | for (var i = 0; i < 1e7; i++) { 698 | if ((new Date().getTime() - start) > milliseconds){ 699 | break; 700 | } 701 | } 702 | } 703 | 704 | // http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript 705 | // user: broofa 706 | function getUUID() { 707 | var id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 708 | var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); 709 | return v.toString(16); 710 | }); 711 | 712 | return id; 713 | } 714 | -------------------------------------------------------------------------------- /docu/maindocumentation/Architektur.tex: -------------------------------------------------------------------------------- 1 | \chapter{Architektur} 2 | 3 | \section{Arbeitsablauf zur Bearbeitung eines Issue} 4 | Um eine erfolgreiche Zusammenarbeit zu gewährleisten, sind allgemein gültige Regeln nötig. Insbesondere wird festgelegt, wie die einzelnen Arbeitsschritte ablaufen sollten, um ein Issue zu bearbeiten. Zudem werden weiterhin die Zuständigkeiten geregelt. 5 | 6 | \subsection{Verwaltung der zu bearbeitenden Issues} 7 | Die zu bearbeitenden Issues werden auf GitHub unter Issues (\href{https://github.com/Transport-Protocol/MBC-Ping-Pong/issues}{https://github.com/Transport-Protocol/MBC-Ping-Pong/issues}) gepflegt. Um den Verlauf eines Issues darzustellen wird das Kanbanboard von GitHub (\href{https://github.com/Transport-Protocol/MBC-Ping-Pong/projects}{https://github.com/Transport-Protocol/MBC-Ping-Pong/projects}) genutzt. 8 | 9 | \subsection{Erstellen der zu bearbeitenden Issues} 10 | Prinzipiell kann und darf jedes Projektmitglied zu jeder Zeit Issues erstellen. Gerade bei Bugs ist dies ein gewünschtes vorgehen. In der Regel sollten dies jedoch aus Gruppensitzungen hervorgehen und durch den Architekten ausformuliert werden.\newline 11 | Ein Issue besteht aus drei Absätzen: 12 | \begin{itemize} 13 | \item \textbf{Beschreibung} \newline 14 | In der Beschreibung wird allgemein auf den Kontext des Issues eingegangen. 15 | \item \textbf{Anforderung} \newline 16 | In Anforderung wird die Zielvision dargestellt. 17 | \item \textbf{Abnahmekriterien} \newline 18 | In Abnahmekriterien werden alle Punkte aufgeführt, die notwendig sind, um das Issue als erfolgreich bearbeitet anzusehen. 19 | \end{itemize} 20 | 21 | \subsection{Das Kanbanboard} 22 | Das Kanbanboard ist in fünf Abschnitte eingeteilt: 23 | \begin{itemize} 24 | \item \textbf{Selected for Development} \newline 25 | Diese Spalte enthält alle Issues, die der Architekt zur Bearbeitung in nächster Zeit ausgewählt hat. Hier enthaltene Issues sind entweder durch den Architekten einem bestimmten Teammitglied zugeordnet. Diese sollten dann auch vorrangig bearbeitet werden. Oder (dies sollte der Normalfall sein) sie sind niemandem zugeordnet, dann kann sich jedes Teammitglied entscheiden, ob er das Issue bearbeitet. Gründe für das direkte zuweisen können unteranderem sein, dass es eine entsprechende vorhergehende Absprache gab, dass der Architekt das Issue speziell einem Bereich zugehörig sieht bzw. eine spezielle Paarung erreichen möchte, oder aber auch, weil ein Issue schon zu lange in "Selected for Development" verweilt. Hat sich ein Teammitglied für ein Issue entschieden, trägt er sich als Bearbeiter ein und zieht es in auf "In Development". 26 | \item \textbf{In Development} \newline 27 | In dieser Spalte verweilen alle Issues, an denen gerade entwickelt wird. Wenn die Entwicklung an einem Issue abgeschlossen ist, zieht der Bearbeiter das Issue weiter auf "Needs Review". 28 | \item \textbf{Needs Review} \newline 29 | Hier verweilen alle Issues, deren Entwicklung abgeschlossen ist, aber noch nicht geprüft wurde, ob die Abnahmebedingungen erfüllt sind. Normalerweise sollte die Abnahme durch den Architekten erfolgen. Issues, die der Architekt bearbeitet hat, muss das Review von einem anderen Teammitglied gemacht werden. Ein Issue bei dem das Review durchgeführt wird, wird in die Spalte "In Review" verschoben. 30 | \item \textbf{In Review} \newline 31 | Hier sind alle Issues enthalten, die sich gerade im Review befinden. Sind alle Abnahmekriterien erfüllt, und sind durch die Bearbeitung des Issue keine neuen Probleme/Fehler hinzugekommen, wird es in die Spalte "Done" verschoben und das Issue geschlossen. Ist dies nicht der Fall, wird ein Entsprechender Kommentar mit einer möglichst detaillierten Beschreibung des Problems an das Issue angehängt, und es wieder auf "In Development" geschoben. 32 | \item \textbf{Done} \newline 33 | Diese Spalte enthält alle abgeschlossenen Issues. 34 | \end{itemize} 35 | 36 | \subsection{Git} 37 | Hier sind die Verhaltensweisen für die Nutzung von Git aufgeführt. Alles hier nicht aufgeführte kann von jedem Teammitglied nach eigenen ermessen gehandhabt werden. 38 | \begin{itemize} 39 | \item \textbf{Branches} \newline 40 | Für jedes Issue wird ein Branch erstellt, außer es handelt sich um reine Dokumentation (im Ordner Docu). Ein Branchname folgt folgendem Muster: "\# ". Dadurch lässt sich 41 | \item \textbf{Commits} \newline 42 | Commits folgen folgendem Namensschema: "\# ". 43 | \item \textbf{Push und Pull} \newline 44 | Es sollte möglichst häufig gepusht werden, um einen eventuellen Datenverlust zu vermeiden. Beim Pull sollte mit "--rebase" gearbeitet werden, um die Historie möglichst sauber zu halten. 45 | \item \textbf{Merge und Pullrequest} \newline 46 | Bevor ein Issue auf "Needs Review" geschoben wird, ist der Master in den Branch zu mergen und ein Pullrequest (\href{https://github.com/Transport-Protocol/MBC-Ping-Pong/pulls}{https://github.com/Transport-Protocol/MBC-Ping-Pong/pulls}) zu erstellen. Derjenige der das Issue reviewt hat, merget den Branch dann mithilfe des Pullrequests in den Master und löscht ihn. 47 | \end{itemize} 48 | 49 | \section{Meilensteine} 50 | In diesem Abschnitt werden die Meilensteine festgelegt. Hierbei wird beschrieben, was wan erreicht sein sollte. 51 | \subsection{Projekt Aufsetzen} 52 | \begin{itemize} 53 | \item \textbf{Beschreibung}\newline 54 | Die Grundlegenden für die Entwicklung notwendigen anfangs Infrastrukturen sind aufgesetzt. 55 | \item \textbf{Kriterien} 56 | \begin{itemize} 57 | \item \textbf{NodeJS-Server aufsetzen} \newline 58 | Der NodeJS Server ist aufgesetzt und stellt eine statische Website zur Verfügung 59 | \item \textbf{Docker} \newline 60 | Eine einheitliche Umgebung wird durch Docker und Docker-Compose ermöglicht. 61 | \end{itemize} 62 | \item \textbf{Beendet:} 25.11.2016 63 | \end{itemize} 64 | 65 | \subsection{Prototyp (Technik)} 66 | \begin{itemize} 67 | \item \textbf{Beschreibung}\newline 68 | Um die identifizierten technischen Risiken schnellst möglich in den Griff zu bekommen, werden diese möglichst früh bearbeitet. In dem Prototyp (Technik) soll gezeigt werden, das die kritische Technik funktioniert. Dies wird anhand von kleine losgelösten Beispielen, die aber nahe der Zielarchitektur sind gezeigt. 69 | \item \textbf{Kriterien} 70 | \begin{itemize} 71 | \item \textbf{Darstellung} \newline 72 | Es wird gezeigt, das im Webbrowser eine Flüssige Darstellung möglich ist. 73 | \item \textbf{Kollisionserkennung} \newline 74 | Es wird gezeigt, dass eine Kollisionserkennung erreichbar ist. 75 | \item \textbf{Kommunikation mittels WebRTC} \newline 76 | Architektur bedingt ist die Nutzung von WebRTC unumgänglich. Es ist zu zeigen, dass eine Verbindung von mehreren Handys zum Darstellungsmedium möglich ist. 77 | \item \textbf{Steuerung} \newline 78 | Die Steuerung soll über den Touchscreen geschehen. Es ist zu zeigen, dass es möglich ist, die Position des Fingers auf dem Touchscreen im Browser auszulesen. 79 | \item \textbf{Größen der Handys/Tabletts} \newline 80 | Unterschiedliche Handys und Tabletts haben verschiedene Größen und Formen. Somit ist ein Konzept zu erarbeiten, welches diesem Problem bei der Steuerung gerecht wird. 81 | \end{itemize} 82 | \item \textbf{Beendet:} 16.12.2016 83 | \end{itemize} 84 | 85 | \subsection{Release 1.0 (Zwei Spieler)} 86 | \begin{itemize} 87 | \item \textbf{Beschreibung}\newline 88 | In diesem ersten Release ist eine Basisversion des Spieles implementiert. Es können zwei Spieler gegeneinander Spielen, indem sie ihre Schläger mit den Handys steuern. Gleichzeitig ist diese Version die minimal Version und enthält alle "Must"-Features. 89 | \item \textbf{Kriterien} 90 | \begin{itemize} 91 | \item \textbf{Schläger} \newline 92 | Für jeden Spieler existiert ein Schläger, der mit dem Handy Steuer 93 | \item \textbf{Ball} \newline 94 | Es gibt ein Ball, der sich über das Spielfeld bewegt. Kollidiert er mit einem Schläger oder der Wand, an der sich kein Schläger befindet, prallt er davon ab. Es gilt hierbei, dass der Einfallwinkel dem Ausfallwinkel entspricht. Zudem beschleunigt der Ball, wenn er mit einem Schläger kollidiert. Wenn der Ball mit der Wand hinter einem Schläger kollidiert, wir er in die Ausgangsposition versetzt und erhält die Ausgangsgeschwindigkeit. 95 | \item \textbf{Punkte} \newline 96 | Immer wenn der Ball mit der Wand hinter einem Schläger kollidiert, erhält der andere Spieler einen Punkt. 97 | \item \textbf{Spielende} \newline 98 | Das Spiel endet automatisch nach X Spielen (wobei gilt: $X \in \mathbb{N} \land X \mod 2 = 1$ ). \textit{Das genaue X ist noch zu definieren.} 99 | \end{itemize} 100 | \item \textbf{Beendet:} 13.01.2017 101 | \end{itemize} 102 | 103 | \subsection{Release 1.X (Diverse Features)} 104 | \begin{itemize} 105 | \item \textbf{Beschreibung}\newline 106 | Basierend auf der Version 1.0 wird das Spiel weiterentwickelt. Jedoch sind alle Features die hier bearbeitet werden können "Can"-Features. Daher kann es sein, dass das Release 1.X äquivalent zu dem Release 1.0 ist. Zudem sind alle hier genannten möglichen Features noch nicht näher spezifiziert und auf ihre Machbarkeit geprüft. Es gilt jedoch, dass je Umgesetztes Feature die Versionsnummer im Minorbereich um Eins steigt. 107 | \item \textbf{mögliche Kriterien} 108 | \begin{itemize} 109 | \item \textbf{N Spieler} \newline 110 | Mehr als 2 Spieler 111 | \item \textbf{Zusätzliche Hindernisse} \newline 112 | Auf dem Spielfeld sind zusätzliche Hindernisse. 113 | \item \textbf{Highscore-Liste} \newline 114 | Es wird eine Highscore-Liste geführt und angezeigt. 115 | \item \textbf{TBD} \newline 116 | To be discussed. 117 | \end{itemize} 118 | \item \textbf{Beendet:} 24.02.2017 119 | \end{itemize} 120 | 121 | \section{Highlevel View} 122 | In diesem Abschnitt wird die Grob-/Gesamtarchitektur betrachtet. Hierbei wird nicht nur auf wesentliche Schnittstellen und Komponenten eingegangen. Zur engeren Auswahl standen zwei mögliche Ansätze. Es werden beide betrachtet, und erläutert warum der Ansatz 2 umgesetzt werden wird. 123 | \subsection{Ansatz 1} 124 | Der Ansatz 1 verfolgt den Klassischen Client-Server-Ansatz. Hierbei dient der Server als zentrale Instanz, die alle signifikanten Logikoperationen übernimmt. Die Clients dienen lediglich zur Ein-/Ausgabe. 125 | \begin{figure}[ht] 126 | \centering 127 | \includegraphics[width=0.9\textwidth]{architecture/highLevelAnsatz1.png} 128 | \caption{Highlevel Ansatz 1} 129 | \label{fig1} 130 | \end{figure} 131 | \subsubsection{Server} 132 | Der Server stellt die statischen Inhalte (HTML, CSS, JS) bereit. Zudem enthält er die gesamte Spiellogik. Der Server empfängt Steuerinformationen vom ControlClient, verarbeitet diese und sendet einen aktuellen Gamestate an den OutputClient. 133 | \subsubsection{ControlClient} 134 | Der ControlClient erfasst die Steuereingaben des Nutzers und sendet sie an den Server. 135 | \subsubsection{OutputClient} 136 | Der OutputClient empfängt den Gamestate vom Server und aktualisiert die Anzeige entsprechend. 137 | \subsubsection{Analyse} 138 | Einerseits ist dieser Ansatz architektonisch sehr einfach umzusetzen, da es eine zentrale Instanz gibt und Seperation of Concerns Architektur bedingt unterstützt wird. Zudem ist es möglich ein Spiel auf mehreren OutputClients darzustellen. Da sich die Clients mit dem Server verbinden, ist der Einsatz von WebSockets möglich. Mit socket.io gibt es eine sehr gute Abstraktionsschicht für WebSockets. Andererseits kann durch den Einsatz von WebSockets ein nicht zu vernachlässigendes Delay entstehen, da diese auf TCP basieren. Um diesem zu begegnen ist der Einsatz des WebRTC Protokollstacks notwendig. Zudem muss der Server die Zuordnung der ControlClients und OutputClients zu einem Spiel organisieren. Außerdem sind zwei Netzwerkübertragungen von Nöten, damit die Eingabe des Spielers auf dem OutputClient sichtbar wir. Dadurch wird das Netzwerk doppelt belastet, und es ist zweimal das Übertragungsdelay vorhanden. 139 | \subsection{Ansatz 2} 140 | \begin{figure}[ht] 141 | \centering 142 | \includegraphics[width=0.9\textwidth]{architecture/highLevelAnsatz2.png} 143 | \caption{Highlevel Ansatz 2} 144 | \label{fig2} 145 | \end{figure} 146 | \subsubsection{Server} 147 | Der Server stellt die statischen Inhalte (HTML, CSS, JS) bereit. Zudem fungiert er als zentrale Instanz für den WebRTC-Protokollstack 148 | \subsubsection{ExternerServer} 149 | Der externe Server ist ein öffentlicher Stun-/Turn-/Ice-Server, der beispielsweise von Google bereitgestellt wird. 150 | \subsubsection{ControlPeer} 151 | Der ControlPeer erfasst die Steuereingaben des Nutzers und sendet sie an den DisplayPeer. 152 | \subsubsection{DisplayPeer} 153 | Der DisplayPeer ist ein Fat-Client. Er enthält die gesamte Spiellogik und empfängt über den WebRTC-Protokollstack direkt die Steuereingaben des Spielers. Zudem zeigt er das Spiel an. 154 | \subsubsection{Analyse} 155 | Der Ansatz 2 ist architektonisch recht Komplex, da die Aufteilung auf Client und Server wegfällt, und somit die native Unterstützung von Seperation of Concern nicht gegeben ist. Es muss während der Entwicklung verstärkt darauf geachtet werden, das die Trennung von Spiellogik und Ausgabe eingehalten wird. Zu dem ist man auf den WebRTC-Protokollstack angewiesen, da die Peers direkt miteinander Kommunizieren müssen. Außerdem ist man auf einen einzelnen DisplayPeer beschränkt. Andererseits erfolgt die Übertragung der Steuerung direkt von dem ControlPeer an den DisplayPeer, dadurch ist das kleinst mögliche Delay zwischen Eingabe, Verarbeitung und Ausgabe gewährleistet. Außerdem ermöglicht der Einsatz von WebRTC den Einsatz von UDP als Transportprotokoll, wodurch das Delay weiter verringer werden kann, da UDP Verbindungslos ist. 156 | \subsection{Fazit} 157 | Auch wenn der Ansatz 2 zunächst komplexer erscheint und einen geringeren Funktionsumfang bietet, da nur ein AusgabeClient pro Spiel unterstützt wird und WebRTC eingesetzt werden muss, überwiegen doch die Vorteile dieses Ansatzes. Durch die fehlende zweite Netzwerkübertragung und der mögliche Einsatz von UDP, werden Delays minimiert. Gleichzeitig wird mehr Rechenleistung auf die Clients ausgelagert und eine aufwändige Verwaltung, welcher Spieler zu welchem Spiel gehört ist auch nicht notwendig. 158 | 159 | \section{Risiken} 160 | In diesem Kapitel wird auf die Risiken eingegangen, die zu Schwierigkeiten bei der Projektdurchführung führen können. Die Auswirkungen und Eintrittswahrscheinlichkeit werden in 3 Kategorien eingeteilt: "1:Gering, 2:Mittel, 3: Hoch". Das potenzielle Risiko ist das Produkt aus Auswirkungen und Eintrittswahrscheinlichkeit. 161 | \subsection{Technische Risiken} 162 | Zunächst wird auf die technischen Risiken eingegangen 163 | \subsubsection{Darstellung ist nicht möglich} 164 | \begin{itemize} 165 | \item \textbf{Beschreibung:} \newline 166 | Gerade bei Ping-Pong wird die Bewegung des Balls irgendwann sehr schnell. Dies muss trotzdem im Browser darstellbar sein, ohne das es ruckelt. 167 | \item \textbf{Eintrittsgründe;} 168 | \begin{itemize} 169 | \item Das gewählte Grafikframework ist nicht leistungsfähig genug. 170 | \item Das gewählte Grafikframework wurde nicht richtig genutzt. 171 | \item Das Zielsystem ist nicht Leistungsfähig genug. 172 | \end{itemize} 173 | \item \textbf{Folgen:} 174 | \begin{itemize} 175 | \item Das Spielen ist nicht möglich => Projektfehlschlag. 176 | \end{itemize} 177 | \item \textbf{Eintrittswahrscheinlichkeit:} 2 178 | \item \textbf{Auswirkungen:} 3 179 | \item \textbf{Risiko:} 6 180 | \item \textbf{Maßnahmen:} 181 | \begin{itemize} 182 | \item Bereits im Prototyp eine Beispielimplementierung durchführen. 183 | \item Möglichst früh einen Test auf der Zielplattform absolvieren. 184 | \end{itemize} 185 | \end{itemize} 186 | \subsubsection{Kollisionserkennung funktioniert nicht} 187 | \begin{itemize} 188 | \item \textbf{Beschreibung:} \newline 189 | Durch die schnelle Ballbewegung bei Ping-Pong ist eine gute Kollisionserkennung notwendig. 190 | \item \textbf{Eintrittsgründe;} 191 | \begin{itemize} 192 | \item Die gewählte Physicsengine ist nicht leistungsfähig genug. 193 | \item Die gewählte Physicsengine wurde nicht richtig genutzt. 194 | \item Das Zielsystem ist nicht Leistungsfähig genug. 195 | \end{itemize} 196 | \item \textbf{Folgen:} 197 | \begin{itemize} 198 | \item Das Spielen ist nicht möglich => Projektfehlschlag. 199 | \end{itemize} 200 | \item \textbf{Eintrittswahrscheinlichkeit:} 2 201 | \item \textbf{Auswirkungen:} 3 202 | \item \textbf{Risiko:} 6 203 | \item \textbf{Maßnahmen:} 204 | \begin{itemize} 205 | \item Bereits im Prototyp eine Beispielimplementierung durchführen. 206 | \item Möglichst früh einen Test auf der Zielplattform absolvieren. 207 | \end{itemize} 208 | \end{itemize} 209 | \subsubsection{Kommunikation mittels WebRTC funktioniert nicht} 210 | \begin{itemize} 211 | \item \textbf{Beschreibung:} \newline 212 | Um die Übertragung in akzeptabler Geschwindigkeit zu gewährleisten, ist der Einsatz von WebRTC unausweichlich. WebRTC ist jedoch absolutes Neuland für das gesamte Team. 213 | \item \textbf{Eintrittsgründe;} 214 | \begin{itemize} 215 | \item WebRTC wird nicht korrekt genutzt. 216 | \item Im Zielnetzwerk wird die Verwendung durch Firewalls behindert. 217 | \end{itemize} 218 | \item \textbf{Folgen:} 219 | \begin{itemize} 220 | \item Die Architektur muss von Fat-Client Ansatz auf einen Serverzentrierten Ansatz umgestellt werden => Projektverzögerung. 221 | \end{itemize} 222 | \item \textbf{Eintrittswahrscheinlichkeit:} 2 223 | \item \textbf{Auswirkungen:} 2 224 | \item \textbf{Risiko:} 4 225 | \item \textbf{Maßnahmen:} 226 | \begin{itemize} 227 | \item Bereits im Prototyp eine Beispielimplementierung durchführen. 228 | \item Möglichst früh einen Test auf der Zielplattform absolvieren. 229 | \end{itemize} 230 | \end{itemize} 231 | \subsubsection{Steuerung ist nicht möglich} 232 | \begin{itemize} 233 | \item \textbf{Beschreibung:} \newline 234 | Das auslesen der Position des Fingers auf dem Bildschirm über den Browser funktioniert nicht, bzw. nicht schnell genug. 235 | \item \textbf{Eintrittsgründe;} 236 | \begin{itemize} 237 | \item Zu altes Handy verwendet (Browser unterstützt es nicht). 238 | \item Schnittstelle nicht korrekt verwendet 239 | \end{itemize} 240 | \item \textbf{Folgen:} 241 | \begin{itemize} 242 | \item Das Spiel ist nicht Steuerbar => Projektfehlschlag. 243 | \end{itemize} 244 | \item \textbf{Eintrittswahrscheinlichkeit:} 1 245 | \item \textbf{Auswirkungen:} 3 246 | \item \textbf{Risiko:} 3 247 | \item \textbf{Maßnahmen:} 248 | \begin{itemize} 249 | \item Bereits im Prototyp eine Beispielimplementierung durchführen. 250 | \end{itemize} 251 | \end{itemize} 252 | \subsection{Konzeptuelle Risiken} 253 | \subsubsection{Steuerung ist nicht Fair} 254 | \begin{itemize} 255 | \item \textbf{Beschreibung:} \newline 256 | Gerade mit dem Prinzip des Browser basierten Ansatzes werden sehr viele unterschiedliche Endgerätetypen angesprochen. Jedes Endgerät hat jedoch eine andere Bildschirmgröße und Pixeldichte. 257 | \item \textbf{Eintrittsgründe;} 258 | \begin{itemize} 259 | \item Es möchten Personen mit unterschiedlichen Endgeräten gegeneinander antreten 260 | \end{itemize} 261 | \item \textbf{Folgen:} 262 | \begin{itemize} 263 | \item Das Spiel ist unfair => geringere Akzeptanz. 264 | \end{itemize} 265 | \item \textbf{Eintrittswahrscheinlichkeit:} 3 266 | \item \textbf{Auswirkungen:} 1 267 | \item \textbf{Risiko:} 3 268 | \item \textbf{Maßnahmen:} 269 | \begin{itemize} 270 | \item Das Risiko wird durch die weitergehende Analyse, und dem Entwurf eines möglichst Fairen Steuerungskonzeptes verringert. 271 | \item Ggf. zum Teil ignorieren 272 | \end{itemize} 273 | \end{itemize} 274 | \subsection{Organisatorische Risiken} 275 | \subsubsection{Temporärer Personalausfall} 276 | \begin{itemize} 277 | \item \textbf{Beschreibung:} \newline 278 | Es kann jederzeit zu einem temporären Personalausfall kommen. 279 | \item \textbf{Eintrittsgründe;} 280 | \begin{itemize} 281 | \item Krankheit. 282 | \item Klausuren. 283 | \item Unmotiviertheit. 284 | \end{itemize} 285 | \item \textbf{Folgen:} 286 | \begin{itemize} 287 | \item Arbeitskraftmangel => Projektverzögerung. 288 | \item Fachwissensmangel => Projektverzögerung. 289 | \end{itemize} 290 | \item \textbf{Eintrittswahrscheinlichkeit:} 3 291 | \item \textbf{Auswirkungen:} 1 292 | \item \textbf{Risiko:} 3 293 | \item \textbf{Maßnahmen:} 294 | \begin{itemize} 295 | \item Puffer einplanen. 296 | \item Möglichst wenig Must-Anforderungen definieren. 297 | \end{itemize} 298 | \end{itemize} 299 | \subsubsection{Kompletter Personalausfall} 300 | \begin{itemize} 301 | \item \textbf{Beschreibung:} \newline 302 | Es kann jederzeit zu einem temporären Personalausfall kommen. 303 | \item \textbf{Eintrittsgründe;} 304 | \begin{itemize} 305 | \item Krankheit. 306 | \item Unmotiviertheit. 307 | \end{itemize} 308 | \item \textbf{Folgen:} 309 | \begin{itemize} 310 | \item Arbeitskraftverlust => Projektverzögerung. 311 | \item Fachwissensverlust => Projektverzögerung. 312 | \end{itemize} 313 | \item \textbf{Eintrittswahrscheinlichkeit:} 2 314 | \item \textbf{Auswirkungen:} 2 315 | \item \textbf{Risiko:} 4 316 | \item \textbf{Maßnahmen:} 317 | \begin{itemize} 318 | \item Puffer einplanen. 319 | \item Möglichst wenig Must-Anforderungen definieren. 320 | \end{itemize} 321 | \end{itemize} 322 | 323 | \section{Reviews} 324 | \subsection{\href{https://github.com/Transport-Protocol/MBC-Ping-Pong/issues/3}{Auswahl einer geeigneten 2D-/Physiksengine}} 325 | \begin{itemize} 326 | \item \textbf{Datum:} 05.12.16 327 | \item \textbf{Prüfung der Anforderungen:} 328 | \begin{itemize} 329 | \item \checkmark Aufruf der Seite http://:/test2DEngine.html zeigt die Testseite. 330 | \item \checkmark Ein quadratischer Bereich mit einer Form in der Mitte auf dem Bildschirm wird dargestellt. 331 | \item \checkmark In diesem Bereich bewegt sich eine Form (am besten eine Kugel) waagerecht von Links nach Rechts. 332 | \item \checkmark Kollidiert die Form mit der rechten Wand, bewegt sich die Form von Rechts nach Links. 333 | \item \checkmark Kollidiert die Form mit der linken Wand, bewegt sich die Form von Links nach Rechts. 334 | \end{itemize} 335 | \item \textbf{Prüfung des Codes:} 336 | \begin{itemize} 337 | \item Der Code ist strukturiert und lesbar. 338 | \item Bis auf 2 Zeilen ist der Code verständlich: 339 | \begin{itemize} 340 | \item this.ball.body.immovable = true; \newline 341 | Ein besserer Kommentar, warum wäre gut gewesen. Nach einigem suchen, gehe ich davon aus, dass nur Objekte, die mit dem Ball kollidieren, beeinflusst werden, aber nicht der Ball selbst.\newline 342 | Dieses Verhalten ist zunächst OK, sollte jedoch bei späteren Features noch einmal geprüft werden. 343 | \item this.ball.body.bounce.set(1); \newline 344 | Es funktioniert in der momentan verwendeten Version, daher ist es OK, sollte jedoch geprüft werden. Laut Dokumentation (\href{http://phaser.io/docs/2.6.2/Phaser.Physics.Ninja.Body.html\#bounce}{http://phaser.io/docs/2.6.2/Phaser.Physics.Ninja.Body.html\#bounce}) Sollte der Wert zwischen 0 und 1 liegen, also nicht 1.0 sein. Empfohlen wird ein wert von 0.999. Eine Prüfung ergab, das auch Werte oberhalb von 1.0 akzeptiert werden. Dies führt dann zu einer Beschleunigung des Balls. 345 | \end{itemize} 346 | \end{itemize} 347 | \end{itemize} 348 | 349 | \FloatBarrier 350 | 351 | \section{Prototyp} 352 | In diesem Kapitel wird der Prototyp behandelt. 353 | 354 | \subsection{Schnittstellen} 355 | Dieses Diagramm (Abbildung \ref{Prototyp Schnittstellen - Klassendiagram}) dient dem gemeinsamen Verständnis, welche Klasse welche Schnittstelle bietet. 356 | \begin{figure}[ht] 357 | \centering 358 | \includegraphics[width=0.9\textwidth]{architecture/PrototypeClassDiagram.png} 359 | \caption{Prototyp Schnittstellen - Klassendiagram} 360 | \label{Prototyp Schnittstellen - Klassendiagram} 361 | \end{figure} 362 | 363 | \subsection{Fachlicher Ablauf} 364 | Im fachlichen Ablauf (Abbildung \ref{Prototyp fachlicher Ablauf - Sequenzdiagramm}) wird dargestellt, wie sich das Programm verhalten soll. Es wird jedoch nicht auf genaue Implementierungsdetails wert gelegt. Er dient dem allgemeine Verständnis, wie der Programmablauf sein soll. Auch die Funktionsnamen bei fremd-APIs dienen lediglich der Beschreibung, welches Verhalten von der API erwartet wird. \textbf{Allerdings sollte der Ablauf später im Programmcode ersichtlich sein.} 365 | \begin{figure}[ht] 366 | \centering 367 | \includegraphics[width = 0.9\textheight, angle=270]{architecture/PrototypeSequenceDiagram.png} 368 | \caption{Prototyp fachlicher Ablauf - Sequenzdiagramm} 369 | \label{Prototyp fachlicher Ablauf - Sequenzdiagramm} 370 | \end{figure} 371 | 372 | \FloatBarrier 373 | 374 | \section{Release 1.0} 375 | \subsection{MookUps} \label{mookups} 376 | Die Mookups (\ref{Mookup - Initial} - \ref{Mookup - GameEnded}) zeigen die Zustände des Spiels und die einzelnen Komponenten (in den MookUps durch grüne Namen gekennzeichnet) werden beschrieben. Im Abschnitt \ref{states} wird gezeigt, wie die einzelnen States in einander Überführt werden. 377 | \subsubsection{Beschreibung der Komponenten} 378 | \begin{itemize} 379 | \item \textbf{Spieler 1:} Spieler, der den Schläger auf der linken Spielfeldseite steuert. 380 | \item \textbf{Spieler 2:} Spieler, der den Schläger auf der rechten Spielfeldseite steuert. 381 | \item \textbf{Spielfeld:} Bereich, in dem das Spiel durch Phaser dargestellt wird. 382 | \item \textbf{Wand 1:} Wand hinter dem Schläger 1. Wird diese getroffen, erhält Spieler 2 einen Punkt. 383 | \item \textbf{Wand 2:} Wand hinter dem Schläger 2. Wird diese getroffen, erhält Spieler 1 einen Punkt. 384 | \item \textbf{Wand 3:} Seitenwände, die lediglich zur Spielfeldbegrenzung dienen. 385 | \item \textbf{Schläger 1:} Schläger der durch Spieler 1 gesteuert wird. Er kann sofort nach der Erstellung durch Spieler 1 bewegt werden. 386 | \item \textbf{Schläger 2:} Schläger der durch Spieler 2 gesteuert wird. Er kann sofort nach der Erstellung durch Spieler 2 bewegt werden. 387 | \item \textbf{Ball:} Der Ball bewegt sich über das Spielfeld, und muss mit den Schlägern davon abgehalten werden, die Wand hinter den Schlägern zu treffen. Trifft der Ball Wand 1 oder 2, wird er in die Ausgangsposition gesetzt und erhällt seine Ausgangsgeschwindigkeit, sowie eine zufällig Richtung. Trifft er Wand 3 oder einen Schläger, prallt er von diesem ab und wird beschleunigt. 388 | \item \textbf{Punkte:} Anzeige des aktuellen Spielstandes, im Format : . 389 | \item \textbf{JoinGamePanel:} Hier werden alle Informationen angezeigt, die benötigt werden, um einem Spiel beizutreten. 390 | \begin{itemize} 391 | \item \textbf{QR-Code:} URL 1 als QR-Code 392 | \item \textbf{URL 1:} URL, um dem Spiel beizutreten. 393 | \end{itemize} 394 | \item \textbf{Timer:} Restzeit bis zum Eintreten eines Events. 395 | \end{itemize} 396 | \begin{figure}[!htb] 397 | \centering 398 | \includegraphics[width=0.65\textwidth]{architecture/Mookup - Initial.png} 399 | \caption{Mookup - Initial} 400 | \label{Mookup - Initial} 401 | \end{figure} 402 | 403 | \begin{figure}[!htb] 404 | \centering 405 | \includegraphics[width=0.65\textwidth]{architecture/Mookup - FirstPlayerConnected.png} 406 | \caption{Mookup - FirstPlayerConnected} 407 | \label{Mookup - FirstPlayerConnected} 408 | \end{figure} 409 | 410 | \begin{figure}[!htb] 411 | \centering 412 | \includegraphics[width=0.65\textwidth]{architecture/Mookup - SecondPlayerConnected.png} 413 | \caption{Mookup - SecondPlayerConnected} 414 | \label{Mookup - SecondPlayerConnected} 415 | \end{figure} 416 | 417 | \begin{figure}[!htb] 418 | \centering 419 | \includegraphics[width=0.65\textwidth]{architecture/Mookup - GameRunning.png} 420 | \caption{Mookup - GameRunning} 421 | \label{Mookup - GameRunning} 422 | \end{figure} 423 | 424 | \begin{figure}[!htb] 425 | \centering 426 | \includegraphics[width=0.65\textwidth]{architecture/Mookup - GameEnded.png} 427 | \caption{Mookup - GameEnded} 428 | \label{Mookup - GameEnded} 429 | \end{figure} 430 | 431 | \FloatBarrier 432 | \subsection{States} \label{states} 433 | Hier wird gezeigt, wie die States ineinander über gehen. Zu den grünen Zuständen gibt es Mookups in \ref{mookups}. 434 | \begin{figure}[!htb] 435 | \centering 436 | \includegraphics[width=0.9\textwidth]{architecture/StateMachineRelease1.0.png} 437 | \caption{Statemachine Release 1.0} 438 | \label{Statemachine Release 1.0} 439 | \end{figure} 440 | \FloatBarrier 441 | \subsection{Klassendiagramm} \label{states} 442 | \begin{figure}[!htb] 443 | \centering 444 | \includegraphics[width=0.9\textwidth]{architecture/Release1.0ClassDiagramControlPeer.png} 445 | \caption{ControlPeer ClassDiagram Release 1.0} 446 | \label{ControlPeer ClassDiagram Release 1.0} 447 | \end{figure} 448 | \begin{figure}[!htb] 449 | \centering 450 | \includegraphics[width=0.9\textwidth]{architecture/Release1.0ClassDiagramDisplayPeer.png} 451 | \caption{DisplayPeer ClassDiagram Release 1.0} 452 | \label{DisplayPeer ClassDiagram Release 1.0} 453 | \end{figure} 454 | -------------------------------------------------------------------------------- /docu/maindocumentation/Frontend.tex: -------------------------------------------------------------------------------- 1 | \chapter{Frontend} 2 | \section{Ideensammlung} 3 | In diesem Teil finden alle Ideen und Gedanken zu dem Projekt Platz welche ich vor Projektbeginn hatte. 4 | \subsection{Allgemeine Ideen} 5 | \paragraph{Partikeleffekte} 6 | \mbox{}\\ 7 | Bei Kollision des Balles mit den Wänden, den Schlägern oder gar anderen Bällen wäre ein Partikeleffekt sehr schön. 8 | \newline 9 | Ebenfalls könnte man mit Partikeleffekten den Ballverlust (Zerstörung des Balles in den ''Toren'') sehr schön grafisch unterstreichen. 10 | \newline 11 | Möglicherweise dafür geeignete Engine: 12 | \href{http://soulwire.github.io/sketch.js/}{Sketch.js} 13 | \paragraph{Multiplayer} 14 | \mbox{}\\ 15 | Ideen zu Spielen mit mehr als 2 Spielern. 16 | \begin{itemize} 17 | \item Punkt bei Ballverlust erhält der letzte Spieler. Spiel merkt sich letzte 2 Ballkontakte sodass der Abschläger erkannt werden kann. 18 | \item Spieler \& Ball färben. Man könnte die Schläger Färben und die Bälle bei Ballkontakt der eigenen Farbe zuweisen. So erhalten die Spieler einen Visuellen Indikator wer Punkte bekommen kann. 19 | \end{itemize} 20 | \newpage 21 | \paragraph{Schläger} 22 | \begin{itemize} 23 | \item 24 | \textbf{Bewegung des Schlägers} Bewegung kann Relativ oder Absolut erfolgen. Bei absoluter Bewegung wird der Schläger sehr stark springen und möglicherweise das Spiel zu einfach werden. 25 | \item 26 | \textbf{Geschwindigkeit} Man sollte die Geschwindigkeit des Schläger begrenzen sodass das Spiel schwieriger wird. 27 | \item 28 | \textbf{Begrenzung} Der Schläger braucht 2 Begrenzungen damit er das Spielfeld nicht verlässt. 29 | \item 30 | \textbf{Input der Controller} Der Input sollte so einfach wie möglich gestaltet werden. Am besten nur die Richtung und die Positionsänderung übertragen. 31 | \end{itemize} 32 | \subsection{Spielfelder} 33 | \paragraph{Original Pong} 34 | \mbox{}\\ 35 | Im Orinalen Pong betrugen die Abmaße der Komponenten folgende werte: 36 | \begin{itemize} 37 | \item 38 | Spielfeld Größe: 512*256px 39 | \item 40 | Ball 6*5px 41 | \item 42 | Schläger 2*28px 43 | \item 44 | Schläger Geschwindigkeit 4px pro Intervall 45 | \end{itemize} 46 | \newpage 47 | \paragraph{2 Spieler Normal} 48 | \mbox{}\\ 49 | \begin{figure}[ht] 50 | \begin{center} 51 | \makebox[\textwidth]{\includegraphics[width=300pt]{frontend/Ping-Pong-2p.png}} 52 | \end{center} 53 | \caption{Mockup eines 2-Spieler Feldes} 54 | \label{figx} 55 | \end{figure} 56 | \newline 57 | \paragraph{2 Spieler Breakout} 58 | \mbox{}\\ 59 | Die Idee für dieses Feld war es 2 Klassiker miteinander zu verbinden. Die Spieler zerstören mit einem eigenen Ball die Felder. Das Spiel endet wenn alle Felder zerstört sind. Gewinner ist der Spieler welcher mehr Felder zerstört hat. 60 | \newline 61 | Die Spieler sollten nur den eigenen Ball beeinflussen können. Also muss für den jeweils anderen Spieler eine Barriere errichtet werden. 62 | \newline 63 | \begin{figure}[ht] 64 | \begin{center} 65 | \makebox[\textwidth]{\includegraphics[width=300pt]{frontend/Ping-Pong-Breakout.png}} 66 | \end{center} 67 | \caption{Mockup eines 2-Spieler Feldes im Spielmodus Breakout} 68 | \label{figx} 69 | \end{figure} 70 | \newpage 71 | \paragraph{3 Spieler} 72 | \mbox{}\\ 73 | Als ich die Präsentation des Projektes sah war ich nicht mit den Ideen für einen Multispielermodus zufrieden. Sofort kam mir folgende Idee: 74 | \newline 75 | 3 Spieler spielen in einem abgeschnittenem Dreieck, bzw eines gleichseitigen Sechseckes. Jeder kontrolliert eine Seite. 76 | \newline 77 | \begin{figure}[ht] 78 | \begin{center} 79 | \makebox[\textwidth]{\includegraphics[width=300pt]{frontend/Ping-Pong-3p.png}} 80 | \end{center} 81 | \caption{Mockup eines 3-Spieler Feldes} 82 | \label{figx} 83 | \end{figure} 84 | \newpage 85 | \paragraph{4 Spieler} 86 | \mbox{}\\ 87 | Als weitere Idee kam mir ein Vier-Spieler Feld bei dem sich in einem Quadrat je 2 Spieler gegenüberstehen. 88 | \newline 89 | Mehr als 4 Spieler sind meiner Meinung nach nicht notwendig. Wenn man bedenkt das sich die Spieler vor einem Bildschirm im Flur sammeln. 90 | \newline 91 | Denkbar ist allerdings das man das Konzept für 3 und 4 Spieler für weitere Spieler weiterführt. 92 | \begin{figure}[ht] 93 | \begin{center} 94 | \makebox[\textwidth]{\includegraphics[width=300pt]{frontend/Ping-Pong-4p.png}} 95 | \end{center} 96 | \caption{Mockup eines 4-Spieler Feldes} 97 | \label{figx} 98 | \end{figure} 99 | \newpage 100 | \subsection{Power-Ups} 101 | Kurze Ideensammlung zu möglichen Verstärkungen. 102 | \begin{itemize} 103 | \item 104 | \textbf{Magnet:} Ball wird vom Schläger angezogen. Der Spieler erhält dann die Möglichkeit den Ball zu führen und gezielt abzustoßen. 105 | \item 106 | \textbf{Größerer / Kleinerer Ball:} Ball-Modifikator, denkbar auch eine pulsierende Variante wobei sich der Ball durchgehend in der Größe verändert. 107 | \item 108 | \textbf{Multi-Ball:} Ball wir mehrfach dupliziert. Die verschiedenen Bälle fliegen vom Startpunkt aus in alle Richtungen und zählen alle als normaler Ball. Bei Ballverlust tauchen diese nicht erneut auf. 109 | \item 110 | \textbf{Steuerung Invertieren:} Steuerung des Gegners wird invertiert. 111 | \item 112 | \textbf{Geschwindigkeitsmodifikation des Schlägers:} Eigener Schläger wird schneller oder gegnerischer wird langsamer. 113 | \end{itemize} 114 | \subsection{Statistiken} 115 | Auflistung an Daten welche für eine Statistik relevant wären. 116 | \begin{itemize} 117 | \item 118 | Spieldauer 119 | \item 120 | Ballkontakte je Spieler 121 | \item 122 | Zurückgelegte Distanz des Balles 123 | \item 124 | Zurückgelegte Distanz je Spieler 125 | \item 126 | Genauigkeit der Schläger 127 | \newline 128 | (Ballkontakte / Durchgelassene Bälle) 129 | \end{itemize} 130 | \newpage 131 | \section{Auswahl einer Physics-Engine} 132 | 133 | \begin{quote} 134 | \href{https://github.com/Transport-Protocol/MBC-Ping-Pong/issues/3}{Auswahl einer geeigneten 2D-/Physiksengine \#3} 135 | \newline 136 | Die Darstellung auf dem Anzeigegerät ist über den DOM nicht möglich, da die Bewegung des Balls und der Schläger zu schnell werden. Zudem wird auch die Physik auf dem Anzeigegeräts berechnet. Hierfür ist eine Kollisionserkennung erforderlich. 137 | \end{quote} 138 | Für die physikalisch korrekte Kollision des Balles mit der Spielwelt und den Schlägern haben wir uns entschieden eine Physics-Engine zu verwenden. 139 | 140 | \subsection{Matter.JS} 141 | Matter js ist eine sehr mächtige Engine. Sie unterstützt viele Formen und Physikalische Eigenschaften wie zum Beispiel Masse und wirkende Kräfte auf die jeweiligen Objekte. 142 | Es besteht die Möglichkeit physikalische Objekte zu verbinden um neue Formen zu erzeugen und sogar diese Elastisch erscheinen zu lassen, so ist es beispielsweise möglich Stoff oder schwingende Seile zu erstellen. 143 | Nach mehrstündiger Einarbeitung kam ich zu dem Schluss das diese Engine für unser Projekt nicht geeignet ist. 144 | Gründe hierfür sind: 145 | \begin{itemize} 146 | \item 147 | \textbf{Zu Komplex:} 148 | Die Einrichtung des Spielfeldes erwies sich als überaus schwierig. Gute Anleitungen für einfache Szenarien fehlten, die verfügbaren Anleitungen sind zu grundlegend beschrieben. 149 | Ebenfalls half die Anleitung der Engine nicht bei der Verwendung der einzelnen Komponenten. 150 | \item 151 | \textbf{Ankerpunkt der Objekte:} 152 | Die Positionierung der Objekte bezog sich immer auf den Mittelpunkt des Objektes, man kann beispielsweise kein Rechteck von (x,y) nach (x1,y1) erstellen sondern muss den Mittelpunkt und die Abmaße des Objektes angeben. 153 | \item 154 | \textbf{Physikalische Eigenschaften:} 155 | Physik nicht immer korrekt. Die Engine sollte den Ball richtig von einer Ebene abprallen lassen, diese Engine allerdings lies den Ball teilweise an Plattformen abrollen obwohl keine Schwerkraft vorhanden war. Ich schließe darauf das die Engine Reibungskräfte und vielleicht sogar Anziehungskräfte zwischen den Objekten herstellt. Für unser Projekt ist dies aber nicht zu gebrauchen. 156 | \item 157 | \textbf{Komplexes Debugging} 158 | Während meiner Versuche bin ich immer wieder auf Probleme gestoßen. Einige der Debugausgaben ließen sich gut ableiten und waren hilfreich. 159 | Allerdings bin ich auch auf einige Probleme gestoßen welche nicht in den Debugausgaben behandelt wurde. Meine letzten Versuche endeten alle darin das der Browser gecrasht ist aufgrund eines Memory-leaks. 160 | \end{itemize} 161 | MatterJS ist eine sehr mächtige Engine, und für viele Einsatzgebiete bestimmt ideal geeignet. Allerdings halte ich die Engine aus den oben genannten Gründen für ungeeignet. Vor allem die Physik scheint für unsere Zwecke gänzlich ungeeignet da sie zu viel berücksichtigt. 162 | \subsection{Phaser.io} 163 | Phaser.io beschreibt sich selber als html5 Game Framework. Es wurde nach dem mobile-first Prinzip entwickelt und ist opensource. 164 | Die Entwicklung des gewünschten Prototypen erwies sich als sehr leicht, da es viele gute Beispiele gibt. 165 | Die Engine unterstützt von Haus aus eine Arcade-Physic, diese scheint perfekt für unser Projekt: Sie beinhaltet Kollisions und Bewegungsfunktionen für den 2-Dimensionalen Raum. 166 | \subsection{Erstellung eines Prototypen} 167 | In Phaser erstellt man ein Spiel über den Aufruf 'new Phaser.Game(...) die ersten Beiden Argumente geben die Dimensionen des Spielfeldes an, also die Weite und Höhe in Pixeln. 168 | \newline 169 | Der Nächste Parameter bestimmt die Render-Engine. 170 | Mögliche Werte sind 'Phaser.CANVAS', 'Phaser.WEBGL' oder 'Phaser.AUTO', wenn 'Phaser.AUTO' verwendet wird so probiert die Engine erst WebGL aus und für den Fall das der Browser WebGL nicht unterstützt wird Canvas verwendet. 171 | \newline 172 | Das nächste Argument gibt das Ziel im DOM an, wenn man diesen Parameter nicht setzt wird das Spiel einfach im Body angehängt. 173 | \newline 174 | Man kann als 5. Argument ein Startzustand angeben. Zustände kann man sich wie Spielszenen vorstellen. 175 | Ich habe mich dafür entschieden die Spielszene erst später hinzuzufügen, das bietet mir den Vorteil das ich diesem Zustand einen Namen geben kann und diesen Später erneut verwenden könnte. 176 | \newpage 177 | Eine Szene bzw. einen Spielzustand kann man per game.state.add('name',State) vobei 'game' die Instanz des Spiels darstellt. 178 | Gestartet wird der Zustand per: 'game.state.start('name)' 179 | Ein Zustand ist wie folgt aufgebaut: 180 | \begin{lstlisting} 181 | { 182 | preload: function () { 183 | 184 | }, 185 | 186 | create: function () { 187 | 188 | }, 189 | 190 | update: function () { 191 | 192 | }, 193 | }; 194 | \end{lstlisting} 195 | Zu den einzelnen Funktionen: 196 | \begin{itemize} 197 | \item 198 | \textbf{preload:} Diese Funktion ist für das Vorladen von Assets gedacht. Beispielsweise Sprites oder Sounds werden hier vorgeladen damit während das Spiel läuft ohne Ladezeit zur Verfügung stehen. 199 | \item 200 | \textbf{create:} Hier werden alle Objekte erstellt die mit dem Beginn des Spielzustandes vorhanden sein sollen. Hier kann man auch Starteigenschafen wie Geschwindigkeit, Schwerkraft oder Richtung setzen. 201 | \item 202 | \textbf{update:} Die update Funktion wird für die Berechnung jedes Frames aufgerufen. In dieser Funktion werden beispielsweise Kollisionen überprüft und darauf reagiert. 203 | \end{itemize} 204 | \newpage 205 | Für den Ball des Ping Pong Prototypen habe ich diese Funktionen wie folgt erstellt: 206 | \begin{itemize} 207 | \item 208 | \textbf{preload:} 209 | \newline 210 | Laden des Sprites in den Namen 'ball': 211 | \newline 212 | \begin{lstlisting} 213 | game.load.image('ball','assets/testBall.png'); 214 | \end{lstlisting} 215 | Bekanntmachung der Ball Variable: this.ball 216 | \item 217 | \textbf{create:} 218 | \newline Erstellen des Balls und einstellen des Ausrichtungspunktes in die Mitte des Sprites: 219 | \begin{lstlisting} 220 | this.ball = 221 | game.add.sprite(game.world.centerX, game.world.centerY, 'ball'); 222 | this.ball.anchor.set(0.5, 0.5); 223 | \end{lstlisting} 224 | Aktivieren der Physik für den Ball: 225 | \begin{lstlisting} 226 | game.physics.startSystem(Phaser.Physics.ARCADE); 227 | game.physics.enable(this.ball, Phaser.Physics.ARCADE); 228 | \end{lstlisting} 229 | Als nächstes hab ich den Ball so konfiguriert das er mit den Spielfeldrändern kollidiert: 230 | \begin{lstlisting} 231 | this.ball.checkWorldBounds = true; 232 | this.ball.body.collideWorldBounds = true; 233 | \end{lstlisting} 234 | Damit der Ball keine zusätzliche Geschwindigkeit beim Kollidieren mit einem anderem sich bewegendem Objekt erhält habe ich ihn 'unbeweglich' gemacht, damit erhält der Ball keine zusätzlichen Impulse von anderen Objekten. 235 | \begin{lstlisting} 236 | this.ball.body.immovable = true; 237 | \end{lstlisting} 238 | Und das er keine Geschwindigkeit verliert beim Abprallen: 239 | \begin{lstlisting} 240 | this.ball.body.bounce.set(1); 241 | \end{lstlisting} 242 | Als letztes musste ich dem Ball nur noch einen Startimpuls geben: 243 | \begin{lstlisting} 244 | this.ball.body.velocity.setTo(200,0); 245 | \end{lstlisting} 246 | \item 247 | \textbf{update:} 248 | \newline Eine Update Funktion war nicht notwendig da der Ball im Prototypen nur mit dem Spielfeld kollidieren soll. 249 | Für die späteren Versionen muss hier die Kollision mit den Schlägern und anderen Objekten definiert werden. 250 | \end{itemize} 251 | \newpage 252 | Der fertige Prototyp sieht so aus: 253 | \begin{figure}[ht] 254 | \includegraphics[scale=1]{frontend/prototype-0.png} 255 | \centering 256 | \caption{Erster Prototyp} 257 | \label{figx} 258 | \end{figure} 259 | \newpage 260 | \section{Analyse - Steuerung} 261 | \begin{quote} 262 | \href{https://github.com/Transport-Protocol/MBC-Ping-Pong/issues/11}{Analyse - Steuerung \#11} 263 | \newline 264 | \textit{Die Steuerung des Spiels erfolgt über das Handydisplay. Jedes Handy ist unterschiedlich groß, zudem sollte der Schläger nicht springen können.} 265 | \end{quote} 266 | Es zu analysieren wie sich verschiedene Auflösungen und verschiedene Pixeldichten auf das Spielgefühl auswirken können. 267 | Denkbar ist das Geräte mit einer geringen Pixeldichte gegenüber Spielern mit einer hohen Pixeldichte im Vorteil sind da aus Geräten mit einer hohen Pixeldichte sehr viel kleinere Gesten zur Steuerung ausreichen. 268 | \newline 269 | \textbf{Zielsetzung dieser Analyse} 270 | \begin{enumerate} 271 | \item 272 | \textbf{Feststellung} Es soll festgestellt werden in wie weit sich ein Unterschied der Pixeldichte auf die Spielweise auswirken kann. 273 | \item 274 | \textbf{Behandlung} Es soll geprüft werden ob sich eine mögliche Unfairness beheben lässt und ein mögliches Konzept erstellt werden. 275 | \end{enumerate} 276 | \subsection{Feststellung der Auswirkung verschiedener Pixeldichten} 277 | Je nachdem wie die Steuerung implementiert wird die Pixeldichte eines Steuergerätes mehr oder weniger Einfluss auf das Spiel haben. Das birgt die Gefahr das Spieler mit einem Gerät mit einer geringen Pixeldichte gegenüber einem Spieler mit einem Gerät mit hoher Pixeldichte im Nachteil ist da er wesentlich stärkere Bewegungen auf der Touchfläche ausführen muss. 278 | Theoretisch kann dies dazu führen das Spieler auf einem Tablet mit niedriger Auflösung immer im Nachteil zu Hochauflösenden Smartphones sind. 279 | Laut der Android Spezifikation muss die Pixeldichte von Android Geräten mindestens 100 dpi betragen. Ferner sind in den Spezifikationen Pixeldichten bis zu 640dpi (xxxhdpi) definiert. Geräte mit 640dpi wären Geräten mit 100dpi um 640\% überlegen. 280 | \paragraph 281 | \mbox{}\\ 282 | Angenommen die Schläger würden 1:1 um den Pixelabstand der Controller bewegt werden und das Spielfeld habe eine Höhe von 400 Pixeln. 283 | Auf einem Gerät mit 100dpi müsste der Spieler über eine Stecke von 4 cm streichen müssen um den Schläger von einem Ende zum anderen zu bewegen. Auf einem Gerät mit 640dpi müsste der Spieler nur über eine Stecke von unter einem Zentimeter streichen. 284 | \subsection{Behandeln verschiedener Pixeldichten} 285 | \paragraph{Ermitteln von der DPI im Browser} 286 | \mbox{}\\ 287 | Es wird nach vorhanden Lösungen zum erkennen der Pixeldichte in PPI bzw DPI gesucht. 288 | Die gefundenen Beispiele werden dann mit verschiedenen Geräten getestet und die Ergebnisse mit den tatsächlichen DPI verglichen. 289 | \newline\textbf{Die Testgeräte} 290 | \begin{enumerate} 291 | \item \begin{tabular}{cc} 292 | Name & PC Monitor\\ 293 | Diagonale & 23.6zoll\\ 294 | Auflösung & 1920*1080\\ 295 | Pixeldichte & 93 DPI\\ 296 | \end{tabular} 297 | \item \begin{tabular}{cc} 298 | Name & Samsung Galaxy S4\\ 299 | Diagonale & 4.99zoll\\ 300 | Auflösung & 1920*1080\\ 301 | Pixeldichte & 441 DPI\\ 302 | \end{tabular} 303 | \item \begin{tabular}{cc} 304 | Name & Samsung Galaxy Tab A\\ 305 | Diagonale & 9.7zoll\\ 306 | Auflösung & 1024*768\\ 307 | Pixeldichte & 132 DPI\\ 308 | \end{tabular} 309 | \end{enumerate} 310 | \newpage 311 | \paragraph{Formatierung per CSS} 312 | Mithilfe von CSS kann man die Größe von allen Elementen einer Webseite beeinflussen, das sogar in Physikalischen Größen wie Zentimeter oder Zoll. Meine Idee war also mithilfe dieser Funktion die DPI des Gerätes zu ermitteln. 313 | \newline 314 | Hierfür würde ich ein Object auf eine feste Größe formattieren und dann abfragen wie gr0ß das Object in Pixeln ist. 315 | \newline 316 | \newline 317 | Nach kurzer Recherche stieß ich auf die Webseite ''http://www.infobyip.com/detectmonitordpi.php'' 318 | \newline 319 | Die Webseite dient dazu die DPI des Gerätes zu bestimmen, genau auf den CSS Funktionen: Es wird ein Quadrat per CSS auf eine bestimmte Größe, zum Beispiel 1 Zoll, formatiert und dann die Größe in Pixeln ausgelesen. Auf diese Weise lässt sich ein DPI wert berechnen. 320 | \newline 321 | Ich testete die Messung mit den Testgeräten und hab zusätzlich die Größe des Quadrates gemessen um festzustellen wie genau diese Methode ist 322 | \newline 323 | \newline 324 | Tests: 325 | \newline 326 | %\begin{enumerate} 327 | \begin{tabular}{ccc} 328 | & Erwarteter Wert & Ermittelter Wert \\ 329 | PC Monitor 1 & & \\ 330 | Pixeldichte & 93 DPI & \colorbox{red!30}{96 DPI} \\ 331 | Größe des Feldes & 8 cm & \colorbox{red!30}{8.2 cm} \\ 332 | & & \\ 333 | Samsung Galaxy S4 & & \\ 334 | Pixeldichte & 441 DPI & \colorbox{red!30}{288 DPI} \\ 335 | Größe des Feldes & 8 cm & \colorbox{red!30}{1.8 cm} \\ 336 | & & \\ 337 | Samsung Galaxy Tab A & & \\ 338 | Pixeldichte & 132 DPI & \colorbox{red!30}{96 DPI} \\ 339 | Größe des Feldes & 8 cm & \colorbox{red!30}{5.7 cm} \\ 340 | \end{tabular} 341 | %\end{enumerate} 342 | \newline 343 | \newline 344 | Getestet wurde jeweils mit Firefox \& Chrome, der PC Monitor wurde auch noch mit dem Edge Browser getestet. Die Ergebnisse waren allesamt für das Gerät identisch. 345 | \newline 346 | \newline 347 | \textbf{Ergebnis:} Die DPI wurden nicht korrekt erkannt. Die Werte weichen auf dem Mobiltelefon um bis zu \underline{77.5\%} ab. 348 | \newline 349 | Das Ausgegebene Quadrat entsprach nicht den erwarteten Größen. 350 | Scheinbar funktioniert diese Art der Feststellung nicht. 351 | \newpage 352 | \paragraph{Verwenden von 'window.devicePixelRatio'} 353 | \mbox{}\\ 354 | Die Eigenschaft \textit{window.devicePixelRatio} gibt das Verhältnis der Größe der physikalischen Pixel des aktuellen Displays zu der Größe der Geräteunabhängigen-Pixel (\textit{device independent pixels(dips)}) wieder. 355 | \begin{displaymath} 356 | window.devicePixelRatio = physical pixels / dips 357 | \end{displaymath} 358 | 359 | Es wird vor allem dafür genutzt um eine Einheitliche Darstellung von Webinhalten auf verschiedenen Displaygrößen zu erreichen. 360 | \newline 361 | Die Methode klingt sehr vielversprechend, also entschied ich einen Test zu erstellen. Meine Idee ist es ein Quadrat zeichnen zu lassen bei dem die Größe abhängig von dem devicePixelRatio ist und dann die Seitenlänge auf dem Display zu messen. 362 | Mein verwendeter Code: 363 | \begin{lstlisting} 364 | var c=document.getElementById("myCanvas"); 365 | var ctx=c.getContext("2d"); 366 | var w = 100*window.devicePixelRatio; 367 | ctx.rect(20,20,w,w); 368 | ctx.fillText(w,30,30); 369 | ctx.stroke(); 370 | \end{lstlisting} 371 | 372 | \textbf{Tests:} 373 | \newline 374 | \begin{tabular}{ccc} 375 | Gerät & DevicePixelRatio & Größe TestQuadrat \\ 376 | PC Monitor 23.6" & 1.0 & 2.7 cm \\ 377 | Samsung Galaxy Tab A & 1.0 & 1.5 cm \\ 378 | Samsung Galaxy S3 Neo & 2.0 & 1.3 cm \\ 379 | Samsung Galaxy S5 mini & 2.0 & 1.1 cm \\ 380 | Samsung Galaxy S4 & 3.0 & 1.9 cm \\ 381 | Samsung Galaxy S7 edge & 4.0 & >4 cm \\ 382 | \end{tabular} 383 | \paragraph{Ergebnis:} 384 | \mbox{}\\ 385 | Das Ergebnis der Tests ist leider ernüchternd. Die ersten Geräte zeigten das Quardrat allesamt in einer ähnlichen Größe. Doch auf weiteren Testgeräten zeigte sich das die Größe sehr stark variierte. Auf dem Samsung Galaxy S7 edge war das Quadrat sogar sehr viel größer als der vordefinierte Bereich. 386 | \newpage 387 | \paragraph{Suche nach Alternativen zu DevicePixelRatio} 388 | \mbox{}\\ 389 | An vielen Stellen habe ich gelesen das der DevicePixelRatio sehr häufig gerundet wird. Dies fiel ebenfalls bei den Testgeräten auf. 390 | \newline 391 | Also kam mir die Idee eine eigene Version der Funktion zu schreiben. In Javascript kann man mit folgender Funktion testen ob ein bestimmter DevicePixelRatio unterstützt wird. 392 | \begin{lstlisting} 393 | if (window.matchMedia('(-webkit-min-device-pixel-ratio: 1)').matches) 394 | \end{lstlisting} 395 | Meine Idee war es den gewünschten Wert der Funktion schrittweise zu erhöhen und den zuletzt akzeptierten Wert zurückzugeben. 396 | \newline 397 | Daraus ergab sich folgende Funktion: 398 | \begin{lstlisting} 399 | function getDPR() { 400 | var numb = 1.0; 401 | while(window.matchMedia( 402 | '(-webkit-min-device-pixel-ratio: ' + numb + ')' 403 | ).matches) { 404 | numb += 0.1; 405 | } 406 | return numb - 0.1; 407 | } 408 | \end{lstlisting} 409 | Leider ergaben die Test mit dieser Funktion keine nennenswerte Ergebnisse weswegen ich auf eine Auflistung und Auswertung der Ergebnisse in diesem Falle verzichte. 410 | \newline 411 | Die ermittelten Werte entsprachen weitesgehend dem DevicePixelRatio. 412 | \paragraph{Verwendung der CSS3 MediaQuery Eigenschaften} 413 | \mbox{}\\ 414 | Mit CSS3 lassen sich verschiedene Fälle für verschiedene Auflösungen definieren. 415 | \begin{figure}[ht] 416 | \centering 417 | \begin{lstlisting} 418 | @media (resolution: 96dpi) { /* Exakt 96 Bildpunkte pro Zoll */ } 419 | @media (min-resolution: 200dpcm) { /* Mindestens 200 Punkte pro cm */ } 420 | @media (max-resolution: 300dpi) { /* Maximal 300 Punkte pro Zoll */ } 421 | \end{lstlisting} 422 | \caption{Quelle: https:\/\/wiki.selfhtml.org\/wiki\/CSS\/Media\_Queries} 423 | \label{figx} 424 | \end{figure} 425 | \newline 426 | Hiermit ist es möglich für die verschiedenen Pixeldichten die Kontrollfelder anzupassen. Also die Größe vorzugeben für verschiedene DPI. 427 | Allerdings werden die Pixeldichten der Geräte nicht immer richtig erkannt. Es treten dieselben Probleme auf wie bei beiden vorangegangenen Tests: Die Geräte behandeln die DPI zu ungenau. 428 | \newline 429 | Testweise habe ich in geringen staffelungen Fälle für verscheidene Pixeldichten angegeben, allerdings wurde nur der PC richtig dargestellt. Für alle Mobiltelefone scheint eine DPI von circa 220 erkannt zu werden. 430 | \newpage 431 | \subsection{Ergebnis der Analyse} 432 | Es gibt leider keine Möglichkeit eine Webapp mit exakten physikalischen Größen zu gestalten oder gar die Pixeldichte zu erkennen. 433 | \paragraph{Zusammenfassung:} 434 | \begin{itemize} 435 | \item 436 | \textbf{Gestaltung per CSS Einheiten} In CSS besteht die Möglichkeit Größen in CM oder Zoll zu definieren. Leider entsprechen die Werte auf Mobilen Endgeräten nicht den gewünschten Werten. 437 | \item 438 | \textbf{Gestaltung mit dem Device Pixel Ratio} Die Gestaltung per DevicePixelRatio erscheint wesentlich genauer als die Gestaltung mit CSS Längenangaben allerdings ist auch diese Lösung sehr ungenau und lässt sich nicht einheitlich nutzen. 439 | \item 440 | \textbf{Verschiedene CSS3 Fälle für verschiedene Pixeldichten} 441 | Mit den CSS3 Media Queries ist es zumindest ansatzweise Möglich die Pixeldichte der Geräte zu erkennen. Allerdings sich auch sie sehr ungenau und keinesfalls verlässlich. 442 | Die Erstellung von Einzelfällen für viele Pixeldichten ist unsinnig, außerdem scheint sie für Mobilgeräte gänzlich unbrauchbar zu sein. 443 | \end{itemize} 444 | 445 | \paragraph{Idee zur Umgehung der Unfairness} Man könnte die Steuerung so definieren das ein Vorteil der Pixeldichte sich nicht zu sehr auf das Spiel auswirkt. Beispielsweise Kann man eine Maximalgeschwindigkeit der Schläger so definieren das sie auch auf Geräten mit geringer Pixeldichte leicht erreicht werden kann. Dadurch sind allerdings möglicherweise Geräte mit hoher Pixeldichte im Nachteil da man für kurze Bewegungen nur noch minimale Bewegungen auf dem Bildschirm durchführen dürfte. 446 | \newline 447 | Denkbar wäre auch eine Steuerung mit fester Bewegungsgeschwindigkeit sodass nur noch die Richtung der Schläger durch die Controller geregelt wird. Diese Art Steuerung verspricht allerdings nicht viel Spaß oder Reaktionsmöglichkeiten der Spieler. 448 | \newline 449 | \newline 450 | \textbf{Lösungsmöglichkeit:} 451 | \paragraph{Verrechnung der Information zur Pixeldichte} 452 | \mbox{}\\ 453 | Zur fairen Gestaltung der Touchfelder sollten wir den DevicePixelRatio verwenden um zwischen Geräten mit hoher und geringerer Pixeldichte unterscheiden. 454 | \newline 455 | Beispielsweise indem wir den PixelRatio mit der zurückgelegten Stecke in Pixeln verrechnen. 456 | \begin{lstlisting} 457 | movement = screenDistance / window.devicePixelRatio; 458 | \end{lstlisting} 459 | Somit werden Geräte mit einem PixelRatio von 1 nicht benachteiligt. Außerdem hat dies zur Folge das Spieler auf einem Gerät mit einem hohen PixelRatio ihre Schläger nicht unnötig schnell bewegen wenn sie zum Beispiel nur kurze Bewegungen erreichen wollen. 460 | \newline 461 | Ein Problem bleibt jedoch, der PixelRatio ist nicht sehr genau. Um ein Gefühl für die Auswirkungen zu bekommen müssen wir mehrere Testgeräte für die Steuerung verwenden. 462 | \subsection{Flüssige Bewegung} 463 | Um eine flüssige Bewegung zu garantieren sollten wir uns auf einige Eigenschaften der Steuerung festlegen. 464 | \paragraph{Anforderung: Bildschirmposition != Schlägerposition} 465 | \mbox{}\\ 466 | Die Schläger sollen in Abhängigkeit einer Bewegung, also einer Positionsänderung auf dem Bildschirm bewegt werden. Das bedeutet für uns das wir die Information der Controller nicht als Absolute Positionen sehen sondern eher die Änderung während einer Geste betrachten. 467 | \newline 468 | Hierfür sei folgendes Festgelegt: 469 | \begin{itemize} 470 | \item 471 | \textbf{Beginn der Geste:} Eine Geste beginnt mit dem Berühren des Touchfeldes. Die Startposition wird ermittelt und in einer Variable gespeichert. 472 | \item 473 | \textbf{Während der Geste:} Während der Finger sich auf dem Touchfeld bewegt wird die neue Position des Fingers ausgewertet. Die neue Position wird von der alten Position abgezogen wodurch sich eine Differenz ergibt. Diese wird dann mit dem PixelRatio verrechnet und an das Spiel (DisplayPeer) geschickt. 474 | \newline 475 | Der ganze Prozess sollte alle 40ms neu gestartet werden. 476 | \item 477 | \textbf{Ende einer Geste: } Eine Geste endet sobald das Touchfeld nicht mehr berührt wird. Die aktuelle Änderung der position wird auf 0 gesetzt. 478 | \end{itemize} 479 | Wenn der Controller nicht mehr berührt wird so darf sich der Schläger nicht weiter bewegen. Der Controller braucht in diesem Zustand dem Spiel keine Infos übermitteln. 480 | \newline 481 | Moderne Mobiltelefone haben wesentlich höhere Auflösungen als unser Spielfeld groß sein wird. Aus diesem Grund sollte die Bewegung auf einem Maximumwert begrenzt werden. 482 | \newline 483 | Maximal sollte sich ein Schläger über die gesamte Spielfläche in einem Zyklus bewegen können. Es ist denkbar das wir den Wert später nach unten korrigieren müssen um ein gutes Spielgefühl schaffen zu können. 484 | \newpage 485 | \section{Erstellung des Technischen Demonstrators} 486 | Für die Präsentation des Projektstandes war unsere Zielsetzung alle Technischen Hürden überwunden zu haben und einen Funktionsfähigen Prototypen zeigen zu können. 487 | \newline 488 | Dieser Prototyp sollte vor allem den Technischen Durchstich erreicht haben, konkret waren das folgende Anforderungen: 489 | \begin{itemize} 490 | \item 491 | Initialisieren der Lobby durch den Server 492 | \item 493 | Kommunikation der Peers via WebRTC 494 | \item 495 | Einlesen von User Eingaben am ControlPeer 496 | \item 497 | Anzeige der Schläger in Abhängigkeit der Usereingaben am DisplayPeer 498 | \end{itemize} 499 | \subsection{Realisierung des ControllPeers} 500 | Meine Aufgabe war es die Darstellung auf dem DisplayPeer, nach den gegebenen Anforderungen, zu realisieren. 501 | 502 | Es sollten nur die Schläger angezeigt und bewegt werden können. Damit jeder Schläger von den anderen Unterscheidbar bleibt hab ich mich dafür entschieden verschiedene Schläger-Sprites in einem Spritesheet zu speichern zu jedem Spieler eine Sprite ID mit zu geben. 503 | Das hat den Vorteil das die Sprites in einer einzigen Datei lagern und leicht getauscht werden können. 504 | \newline 505 | Laden des Spritesheets (Argumente:Name, Pfad, Weite, Höhe, Anzahl): 506 | \begin{lstlisting} 507 | game.load.spritesheet('paddle', 'assets/testPaddles.png', 9, 100, 6); 508 | \end{lstlisting} 509 | Die Spieler erhalten ihren Sprite in Abhängigkeit zu ihrer ID: 510 | \begin{lstlisting} 511 | function assignPlayerSprite(player) { 512 | player.sprite = game.add.sprite(player.x, player.y, 'paddle'); 513 | player.sprite.frame = player.id; } 514 | \end{lstlisting} 515 | Die Methode wurde in einer Methode verbaut welche einen neuen Spieler erzeugt. Dabei wurde auch geprüft ob die maximale Anzahl an Spielern überschritten wurde. 516 | \newline 517 | Das Spielerobjekt selber bekam eine Funktion um die neuen Eingaben zu seinem Buffer hinzuzufügen sowie eine Update Methode welche vom Spiel aufgerufen wurde um die Position zu aktualisieren. 518 | \newline 519 | In der Update Methode wurden über alle Positionen im Buffer iteriert. Das hat den Hintergrund das später nur Positionsänderungen übertragen werden und dabei alle berücksichtigt werden sollen. 520 | 521 | \section{Erstellung des 2 Spieler Feldes} 522 | \begin{quote} 523 | \href{https://github.com/Transport-Protocol/MBC-Ping-Pong/issues/22}{DisplayPeer - Schläger \#22} 524 | \newline 525 | \textit{Der Schläger ist ein Elementares Element des Spieles. in #11 wurde ein Konzept entwickelt, in welchem 526 | festgelegt wurde, welche Daten an den Schläger übermittelt werden.} 527 | \end{quote} 528 | \begin{quote} 529 | \href{https://github.com/Transport-Protocol/MBC-Ping-Pong/issues/26}{DisplayPeer - InitialState \#26} 530 | \newline 531 | \textit{Der Initiale Zustand wird dargestellt, wenn die Seite geladen wird.} 532 | \end{quote} 533 | Die beiden Issues beschrieben Anforderungen an das Spielfeld für ein 2 Spieler Feld. 534 | \subsection{Problem: Physic Engine} 535 | Bei der Erstellung des Spielfeldes fiel auf das die Kollision mit den Wänden nicht funktioniert. Grund hierfür ist die eingesetzte Physic Engine ''Phaser.Arcade''. 536 | In Phaser hat man die Wahl zwischen verschiedenen Engines. 537 | \begin{itemize} 538 | \item 539 | Arcade 540 | \item 541 | Box2D 542 | \item 543 | Ninja Physics 544 | \item 545 | P2 Physics 546 | \end{itemize} 547 | Wir hatten uns für die Arcade Engine entschieden weil sie am ehesten für unseren Zweck zu passen schien. 548 | \paragraph{Problem 1} 549 | Das erste Problem war das wir keine Kollision zu gedrehten Wänden abfragen konnten. Die Wände wurden schlichtweg nicht erkannt. 550 | Dieses Problem ließ sich theoretisch lösen indem man die BoundingBox mit dreht, oder sie nach dem drehen updated. 551 | \paragraph{Problem 2} 552 | Das zweite Problem war das die Arcade Engine nur Rechteckige und nicht gedrehte Bounding Boxen unterstützt. Das bedeutet das wir für unser 2 Spieler Feld, wo wir nur um 90° gedrehte Wände verwenden, keine weiteren Probleme haben. Allerdings würde mit dieser Engine keinerlei schräge Wand erkennbar sein und damit wäre der 3 - Spieler Modus gestorben. 553 | \newline 554 | \textbf{Resultat: Eine neue Physic Engine muss her} 555 | \subsection{Umstellung auf die P2 Physics} 556 | Ziemlich schnell bin ich auf die P2 Engine gestoßen, da viele Nutzer Ähnliche Probleme mit der Arcade Engine hatten. 557 | \newline 558 | Die Umstellung ging im Grunde recht einfach, allerdings behandelt die Engine sehr vieles anders als die Arcade Engine. 559 | \newline 560 | Viele Funktionen die unter Arcade direkt auf dem Sprite ausgeführt wurden werden in P2 auf den Physikalischen Body ausgeführt. Dazu zählen unter anderem: 561 | \begin{itemize} 562 | \item 563 | BoundingBox des Balls: Vorher wurde der Ball erkannt, bei P2 muss der Body definiert werden mit 564 | \begin{listing} 565 | this.ball.body.setCircle(16); 566 | \end{listing} 567 | \item 568 | Drehung: Vorher konnten Objete per ''obj.angle'' gedreht werden, daraus wurde ''obj.body.angle'' 569 | \item 570 | Beschleunigung: In Arcade musste eine ''obj.velocity'' auf beiden Achsen angegeben werden, daraus wurde ein ''obj.body.thrust()'' aufruf 571 | \item 572 | Bounding Box der Wände: Diese wurde zuerst falsch erkannt da der Sprite gestreckt wird. 573 | Lösung war es erst die transformation des Sprites durchzuführen udn im Anschluss den Physikalischen Body zu erstellen. 574 | Ferner mussten die Bodies in statische Bodies umgewandelt werden da sie sich sonst im Raum bewegen würden. 575 | \item 576 | Drehung der Sprites: Vorher fand die Drehung am Sprite selber statt, dort war der Ankerpunk auf (0,0) definiert. Beim Body ist der Ankerpunkt standartmäßig der Mittelpunkt des Sprites, also musste die Platzierung des Sprites neu überdacht werden. 577 | \item 578 | Bewegung des Schlägers: Die Schläger wurden sonst per x \& y Koordinaten gesetzt, P2 nimmt diese aber nicht an 579 | \end{itemize} 580 | \subsubsection{Problem Tunneling} 581 | Wenn der Ball sich zu schnell bewegt kann es vorkommen das er sich durch Objekte durch bewegt. Genannt wird dieses Problem tunneling. 582 | Behandeln kann man das Problem nur sehr schwer da die Engine selber keine Überprüfung auf Tunneling vornimmt. 583 | \newline 584 | \textbf{Die Engine prüft nur ob an der nächsten Position eine Kollision vorliegt, nicht zwischen den Positionen} 585 | \paragraph{Behandlung: Dicke Wände}\\ 586 | \mbox{}\\ 587 | Es ist möglich das Tunneling zu reduzieren indem man die Wanddicke erhöht. Dadurch kommt es eher zu einer Kollision wenn der Ball sonst aus dem Spielfeld fliegt. 588 | \newline 589 | Am besten werden die Wände nur in ihren Kollisionsboxen aber nicht in der wirklichen Größe verändert. 590 | \paragraph{Behandlung: Begrenzung der Geschwindigkeit} 591 | \mbox{}\\ 592 | Eine weitere Möglichkeit wäre es die Geschwindigkeit zu begrenzen. Allerdings kann dies sehr stark den Spaß am Spiel nehmen. 593 | \paragraph{Behandlung: Nutzung der P2 Funktion ''CCD'' } 594 | \mbox{}\\ 595 | CCD steht für ''Continuous Collision Detection''. 596 | \newline 597 | CCD ist ein recht komplexes Thema, da man versuchen will die Kollisionserkennung sowohl effektiv als auch effizient zu gestalten. 598 | \newline 599 | Kollisionen entstehen nicht immer nur am alten und neuen Standpunkt eines Objektes sondern zwischen den Punkten könnten auch Kollisionen stattfinden. Sehr einfache Algorithmen prüfen diese nicht. 600 | \newline 601 | CCD ist eine Technik um eben diese Zwischenkollisionen ebenfalls effizient zu erkennen. Dabei werden grundsätzlich Zwischenschritte erstellt und geprüft. 602 | \newline 603 | Für die Physic Engine P2 wurde ccd mit eingebaut, ist allerdings standardmäßig deaktiviert. 604 | Zum aktivieren muss der ''ccdSpeedThreshold'' auf einen Wert >=0 gesetzt werden. 605 | \newline 606 | Es gibt 2 einstellbare Variablen 607 | \begin{itemize} 608 | \item 609 | \textbf{ccdSpeedThreshold} Gibt die Geschwindigkeit an ab der die Funktion genutzt wird. Bei Angabe eines sehr geringen Wertes kann es zu Performance Problemen kommen da die ccd Berechnungen recht aufwändig sind und erst ab einer gewissen Geschwindigkeit Sinn machen. 610 | \item 611 | \textbf{ccdIterations} Hiermit lässt sich die Genauigkeit definieren, also die Anzahl der Durchgänge. Eine hohe Zahl erzielt bessere Ergebnisse allerdings wird die Berechnung dadurch deutlich aufwändiger. 612 | \end{itemize} 613 | \newpage 614 | \paragraph{Lösung} 615 | \mbox{}\\ 616 | Es wurden die verschiedenen Lösungen probiert und folgende Resultate beobachtet: 617 | \newline 618 | \textbf{Vergrößern der Hitboxen} 619 | \newline 620 | Dem ersten Eindruck nach wurde das Problem behoben indem die Wände und Schläger dickere Kollisions-Boxen bekamen, unklar ist ob die Todeszonen, also die Wände welche sich hinter den Schlägern befinden, richtig erkannt werden. 621 | \newline 622 | \textbf{Aktivierung von ccd} 623 | \newline 624 | Das aktivieren von ccd schien für einige Geschwindigkeiten das Problem zu beheben. Allerdings nicht bei sehr hohen Geschwindigkeiten, hierzu könnte man die Iterationen noch weiter erhöhen dies hat allerdings eine höhere CPU Auslastung zur Folge. 625 | \newline 626 | \newline 627 | \textbf{Endergebnis: } Die Vergrößerung der Hitboxen erwies sich als gute Lösung da dadurch die CPU nicht unnötig belastet wird. Zusätzlich könnte man noch ccd aktivieren um sicher zu gehen das die Wände der Levelbegrenzungen sicher erkannt werden. 628 | \newpage 629 | \section{Erstellung des 3 und 4 Spieler Modus} 630 | \subsection{Änderungen gegenüber des 2 Spielermodus} 631 | Im 2 Spieler Modus läuft das Spiel etwas anders ab als im Multiplayer Modus. 632 | \begin{enumerate} 633 | \item[Spielfeld] 634 | Für jeden Spielmodus gibt es ein Spielfeld in dem sich alle Spieler gegenüberstehen. 635 | \newline 636 | Im 3 Spieler Modus entspricht dies einem Sechseck wobei immer eine Wand und ein Tor abwechselnd vorhanden sind. 637 | \newline 638 | Im 4 Spieler Modus entspricht das Spielfeld einem Achteck mit unterschiedlichen Seitenlängen. Die Kürzeren Seiten sind für die Wände und die Längeren sind die Tore. 639 | \newline 640 | Mockups beider Spielfelder befinden sich im Abschnitt ''Ideensammling, Spielfelder'' 641 | 642 | \item[Punktvergabe] 643 | Im 2 Spieler Modus hat man für jeden Treffer einen Punkt erhalten und bei erreichen von 10 Punkten hat der Spieler gewonnen. 644 | \newline 645 | In den Multiplayer Spielmodi beginnt jeder Spieler mit 10 Leben und es wird bei jedem Treffen dem entsprechendem Spieler ein Leben abgezogen. 646 | \newline 647 | Wenn ein Spieler kein Leben mehr hat wird er aus dem Spiel genommen und die restlichen Spieler können weiter spielen. 648 | 649 | \item[Steuerung] 650 | Die Bewegung der Schläger war im 2 Spieler Modus nur Vertikal, also auf der Y Achse. Die Implementierung war recht einfach und lies keine anderen Bewegungen zu. 651 | \newline 652 | Im 3 Spieler Modus müssen sich spieler sowohl horizontal als auch Diagonal bewegen können. 653 | \newline 654 | Nach einigen versuchen habe ich mich dafür entschieden dem jeweiligen Spielfeld Pfade für jeden Spieler hinzuzufügen. 655 | \newline 656 | Vom Controller wird nur noch die Y Achse genutzt und diese wird dann auf den Pfad gerechnet um so die neue Position zu bestimmen. 657 | \end{enumerate} 658 | \subsection{Aufgaben} 659 | \begin{itemize} 660 | \item 661 | \textbf{Anpassung der State Machine} 662 | \newline 663 | Die State Machine muss angepasst werden um einen Mehrspielermodus zu erlauben. 664 | \item 665 | \textbf{Anpassung des Wall-Builders} 666 | Im Wallbuilder müssen die neuen Spielfelder definiert werden. Das beinhaltet auch die Position der Punktzahlen und die Spielerpositionen. 667 | \item 668 | \textbf{Anpassung der Spieler Steuerung} 669 | Aktuell ist die Steuerung nicht für Diagonale oder Horizontale Steuerung ausgelegt. Dies könnte die schwierigste Aufgabe bei der Erstellung der Multiplayer Modi sein. 670 | \item 671 | \textbf{Einbinden der Level-Auswahl} 672 | Den Spielern muss im Vorfelde die Möglichkeit gegeben werden den Spielmodus zu wählen. 673 | \item 674 | \textbf{Erstellung der Spieler-Arrays} 675 | \item 676 | \textbf{Anpassung im End-State: Siegerbestimmung} 677 | Es ist denkbar das die Siegerbestimmung geändert werden muss. Aktuell werden Punkte hochgezählt und der Spieler welcher als erstes 10 Punkte erreicht hat gewonnen. Im Multiplayer Modus wird man von 10 leben runter spielen. 678 | \item 679 | \textbf{Anzeige der Punkte je Spieler} 680 | Für jeden Spieler muss die Punktzahl gut sichtbar dargestellt werden. Es muss klar sein welche Score zu welchem Spieler gehört. 681 | \item 682 | \textbf{Erstellung von Wänden nach SpielerTod} 683 | Im Multiplayer Modus spielt man ein Sudden Death Spiel. Also Spieler fliegen beim erreichen von 0 Leben aus dem Spiel. Die anderen müssen weiter spielen können ohne das der Ball durch das dann leere Tor gelangen kann. 684 | \end{itemize} 685 | \subsection{Anpassung der State Machine} 686 | In Zusammenarbeit mit unserem Architekten haben wir die State Machine überarbeitet um einen Mehrspielermodus zu erlauben. 687 | \newline 688 | Grundsätzlich sind folgende Änderungen vorgenommen worden: 689 | \begin{enumerate} 690 | \item 691 | Zustand ''FirstPlayerConnected'' wird zu ''PlayerConnected'', 692 | \newline 693 | Der Zustand wird genutzt solange nicht alle Spieler verbunden sind. In dem Zustand wird jeder verbundene Spieler gezählt und sobald alle Verbunden sind geht es in den Zustand ''AllPlayersConnected'' 694 | \item 695 | Zustand ''SecondPlayerConnected'' wird zu ''AllPlayerConnected'', 696 | \newline 697 | Die Funktion des Zustandes bleibt die selbe: Er dient dazu einen Timer zu starten nachdem Alle Spieler verbunden sind. Danach wird das Spiel gestartet. 698 | \end{enumerate} 699 | \subsection{Anpassung des Spieler Movements} 700 | Aktuell war der Bereich den der Spieler nutzen kann nur oben und unten Begrenzt. Mit der diagonalen Bewegung im 3 Spieler Modus muss die Positionsberechnung angepasst werden. 701 | \newline 702 | Im Wallbuilder werden den Spielern ab sofort Pfade mitgegeben auf denen sie sich bewegen dürfen. Hierbei muss beachtet werden das immer x \& y Positionen beachtet werden. Da der Schläger ansonsten von dem Pfad losgelöst wird. 703 | \newline 704 | Die Lösung sieht wie folgt aus: 705 | \newline 706 | \begin{lstlisting} 707 | var dis = Phaser.Math.distance(path.from.x, path.from.y, 708 | path.to.x, path.to.y); 709 | var dx = ((path.from.x - path.to.x)) / dis; 710 | var dy = ((path.from.y - path.to.y)) / dis; 711 | 712 | var mM = Math.min(pos.y, dis); 713 | 714 | this.body.x = path.to.x + mM * dx; 715 | this.body.y = path.to.y + mM * dy; 716 | \end{lstlisting} 717 | Erklärung: 718 | \newline 719 | Es wird die Distanz des Bewegungspfades genommen (dis) und dann eine relative Änderung pro Achse berechnet. 720 | \newline 721 | mM Steht für ''Max Movement'' und begrenzt die Nutzereingabe. 722 | \newline 723 | Dann wird die Position ausgerechnet mit dem Startpunkt auf dem Pfad plus die relative Änderung multipliziert mit der Bewegung. 724 | \subsection{Einbinden der Levelauswahl} 725 | Die Levelauswahl habe ich in den Ersten State, den ''InitialiseNewGame'' Zustand eingebunden in Form von Buttons für die 3 Spielmodi. 726 | Es wird die gewählte Spielerzahl an den nächsten State gegeben. 727 | \newline 728 | Beim Klick auf einen der Buttons wird der ''Initial'' Zustand gestartet, die Buttons zerstört und in den Game Properties werden Variablen für die Spieleranzahl, Gamemode und verbleibende Spieleranzahl gesetzt. 729 | \subsection{Anpassung des WallBuilders} 730 | Der Wallbuilder muss dahingehend angepasst werden dass nicht nur ein 2 Spieler Feld sondern auch die beiden anderen Felder erstellt werden können. 731 | Hierzu habe ich für jedes Feld ein Objekt erstellt welches wie folgt aufgebaut ist: 732 | \begin{lstlisting} 733 | var field2Player = { 734 | walls: [{from: {x: 0, y: 64}, to: {x: 640, y: 64}}, 735 | {from: {x: 640, y: 440}, to: {x: 0, y: 440}}], 736 | scoreWalls: [{from: {x: 640, y: 64}, to: {x: 640, y: 440}}, 737 | {from: {x: 0, y: 440}, to: {x: 0, y: 64}}], 738 | movingPaths: [{from: {x: 0, y: 64}, to: {x: 0, y: 440}}, 739 | {from: {x: 640, y: 64}, to: {x: 640, y: 440}}] 740 | }; 741 | \end{lstlisting} 742 | Erklärung der Felder: 743 | \begin{itemize} 744 | \item 745 | walls: Die Statischen Wände. Sie dienen nur als Spielfeldbegrenzung. 746 | \item 747 | scoreWalls: Die Wände hinter den Schlägern. Sie sind unsichtbar und der Ball kollidiert nicht mit ihnen solange der Spieler noch im Spiel ist. 748 | \item 749 | movinPaths: Die Pfade die ein Schläger sich bewegen kann. Es gibt start und endpunkt. Zwischen diesen kann der Schläger bewegt werden. 750 | \end{itemize} 751 | Bis auf die Walls entspricht der Array Index immer der Spieler ID. 752 | --------------------------------------------------------------------------------