Merge pull request #12 from LoveEevee/multiplayer

Add online 2-player mode [untested]
This commit is contained in:
Bui 2018-09-12 18:45:15 +01:00 committed by GitHub
commit a8b05df75e
12 changed files with 813 additions and 370 deletions

View File

@ -45,6 +45,7 @@
<script src="/src/js/scalablecanvas.js"></script> <script src="/src/js/scalablecanvas.js"></script>
<script src="/src/js/element.js"></script> <script src="/src/js/element.js"></script>
<script src="/src/js/soundbuffer.js"></script> <script src="/src/js/soundbuffer.js"></script>
<script src="/src/js/p2.js"></script>
</head> </head>
<body> <body>

View File

@ -75,6 +75,7 @@ ul li{
box-shadow: 2px 2px 10px black; box-shadow: 2px 2px 10px black;
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
position: relative;
} }
.opened{ .opened{
@ -127,3 +128,12 @@ ul li{
.difficulty:hover .stars{ .difficulty:hover .stars{
color:white; color:white;
} }
.song.p2:not(.opened)::after,
.difficulty.p2::after{
content:"P2";
display:block;
position:absolute;
bottom:0;
width:100%;
}

View File

@ -1,226 +1,220 @@
function Controller(selectedSong, songData, autoPlayEnabled){ class Controller{
constructor(selectedSong, songData, autoPlayEnabled, multiplayer){
this.selectedSong = selectedSong
this.songData = songData
this.autoPlayEnabled = autoPlayEnabled
this.multiplayer = multiplayer
this.pauseMenu = false
var _this = this; var backgroundURL = "/songs/" + this.selectedSong.folder + "/bg.png"
var _backgroundURL = "/songs/"+selectedSong.folder+"/bg.png"; var songParser = new ParseSong(songData)
this.parsedSongData = songParser.getData()
var _songParser = new ParseSong(songData); //get file content assets.songs.forEach(song => {
var _songData = _songParser.getData(); if(song.id == this.selectedSong.folder){
this.mainAsset = song.sound
}
})
var _game = new Game(this, selectedSong, _songData); this.game = new Game(this, this.selectedSong, this.parsedSongData)
var _view = new View(this, _backgroundURL, selectedSong.title, selectedSong.difficulty); this.view = new View(this, backgroundURL, this.selectedSong.title, this.selectedSong.difficulty)
var _mekadon = new Mekadon(this, _game); this.mekadon = new Mekadon(this, this.game)
var _keyboard = new Keyboard(this); this.keyboard = new Keyboard(this)
var _mainLoop; }
var _pauseMenu = false; run(syncWith){
var _mainAsset this.loadUIEvents()
assets.songs.forEach(song => { this.game.run()
if(song.id == selectedSong.folder){ this.view.run()
_mainAsset = song.sound this.startMainLoop()
if(syncWith){
syncWith.game.getElapsedTime = () => {
return this.game.elapsedTime
}
this.game.setElapsedTime =
syncWith.game.setElapsedTime = time => {
this.game.elapsedTime.ms = time
syncWith.game.elapsedTime.ms = time
}
syncWith.run()
this.syncWith = syncWith
} }
})
this.autoPlayEnabled = autoPlayEnabled
this.run = function(){
_this.loadUIEvents();
_game.run();
_view.run();
_this.startMainLoop();
}
this.loadUIEvents = function(){
$("#song-selection-butt").click(function(){
assets.sounds["don"].play();
_this.songSelection();
});
$("#restart-butt").click(function(){
assets.sounds["don"].play();
_this.restartSong();
});
$("#continue-butt").click(function(){
_this.togglePauseMenu();
});
} }
loadUIEvents(){
this.startMainLoop = function(){ $("#song-selection-butt").click(() => {
assets.sounds["don"].play()
var started=false; this.songSelection()
_mainLoop = setInterval(function(){ })
$("#restart-butt").click(() => {
var ms = _game.getEllapsedTime().ms; assets.sounds["don"].play()
if(ms<0){ //before starting game, offseting the circles this.restartSong()
_game.updateTime(); })
_view.refresh(); $("#continue-butt").click(() => {
} this.togglePauseMenu()
else if(ms>=0 && !started){ //when music shall starts })
_mainAsset.play(_songData.generalInfo.audioWait) }
started=true; startMainLoop(){
} this.mainLoopStarted = false
this.mainLoopRunning = true
if(started){ //Game start here this.mainLoop()
if(!_game.isPaused()){ }
_game.update(); mainLoop(){
_view.refresh(); if(this.mainLoopRunning){
_keyboard.checkGameKeys(); if(this.multiplayer != 2){
} requestAnimationFrame(() => {
_keyboard.checkMenuKeys(); if(this.syncWith){
} this.syncWith.game.elapsedTime.ms = this.game.elapsedTime.ms
}
}, 20); this.mainLoop()
if(this.syncWith){
} this.syncWith.mainLoop()
}
this.getDistanceForCircle = function(){ })
return _view.getDistanceForCircle(); }
var ms = this.game.getElapsedTime().ms
if(!this.game.isPaused()){
if(ms >= 0 && !this.mainLoopStarted){
this.mainLoopStarted = true
}
if(ms < 0){
this.game.updateTime()
}
if(this.mainLoopStarted){
this.game.update()
this.game.playMainMusic()
}
this.view.refresh()
this.keyboard.checkGameKeys()
}
this.keyboard.checkMenuKeys()
}
}
getDistanceForCircle(){
return this.view.getDistanceForCircle()
}
togglePauseMenu(){
this.togglePause()
this.view.togglePauseMenu()
}
displayResults(){
var score = this.getGlobalScore()
var vp
if (score.fail == 0) {
vp = "fullcombo"
this.playSoundMeka("fullcombo", 1.350)
} else if (score.hp >= 50) {
vp = "clear"
} else {
vp = "fail"
}
assets.sounds["game" + vp].play()
setTimeout(() => {
this.mainLoopRunning = false
if(this.multiplayer != 2){
new Scoresheet(this, this.getGlobalScore())
}
}, 7000)
}
displayScore(score, notPlayed){
this.view.displayScore(score, notPlayed)
}
fadeOutOver(){
this.game.fadeOutOver()
this.displayResults()
}
getCurrentTimingPoint(){
return this.game.getCurrentTimingPoint()
}
songSelection(){
$("#music-bg").remove()
this.mainLoopRunning = false
new SongSelect()
}
restartSong(){
this.mainAsset.stop()
this.mainLoopRunning = false
$("#screen").load("/src/views/game.html", () => {
var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled)
taikoGame.run()
})
}
playSoundMeka(soundID, time){
var meka = ""
if(this.autoPlayEnabled && !this.multiplayer){
meka = "-meka"
}
assets.sounds[soundID + meka].play(time)
}
initTiming(){
this.game.initTiming()
}
setHitcircleSpeed(speed){
this.view.setHitcircleSpeed(speed)
}
getHitcircleSpeed(){
return this.game.getHitcircleSpeed()
}
toggleMainMusic(){
this.game.toggleMainMusic()
}
togglePause(){
if(this.syncWith){
this.syncWith.game.togglePause()
}
this.game.togglePause()
}
isPaused(){
return this.game.isPaused()
}
getKeys(){
return this.keyboard.getKeys()
}
setKey(keyCode, down){
return this.keyboard.setKey(keyCode, down)
}
getBindings(){
return this.keyboard.getBindings()
}
getSongData(){
return this.game.getSongData()
}
getElapsedTime(){
return this.game.getElapsedTime()
}
getCircles(){
return this.game.getCircles()
}
getCurrentCircle(){
return this.game.getCurrentCircle()
}
updateCurrentCircle(){
this.game.updateCurrentCircle()
}
isWaitingForKeyup(key, type){
return this.keyboard.isWaitingForKeyup(key, type)
}
waitForKeyup(key, type){
this.keyboard.waitForKeyup(key, type)
}
getKeyTime(){
return this.keyboard.getKeyTime()
}
updateCombo(score){
this.game.updateCombo(score)
}
getCombo(){
return this.game.getCombo()
}
getGlobalScore(){
return this.game.getGlobalScore()
}
updateGlobalScore(score){
this.game.updateGlobalScore(score)
}
autoPlay(circle){
if(this.multiplayer){
p2.play(circle, this.mekadon)
}else{
this.mekadon.play(circle)
}
} }
this.togglePauseMenu = function(){
_this.togglePause();
_view.togglePauseMenu();
}
this.displayResults = function(){
clearInterval(_mainLoop);
var score = _this.getGlobalScore();
if (score.fail == 0) {
vp = 'fullcombo';
_this.playSoundMeka('fullcombo', 1.350);
} else if (score.hp >= 50) {
vp = 'clear';
} else {
vp = 'fail';
}
assets.sounds['game' + vp].play();
setTimeout(function(){
new Scoresheet(_this, _this.getGlobalScore());
}, 7000);
}
this.displayScore = function(score, notPlayed){
_view.displayScore(score, notPlayed);
}
this.fadeOutOver = function(){
_game.fadeOutOver();
_this.displayResults();
}
this.getCurrentTimingPoint = function(){
return _game.getCurrentTimingPoint();
}
this.songSelection = function(){
$("#main-music").remove();
$("#music-bg").remove();
clearInterval(_mainLoop);
new SongSelect();
}
this.restartSong = function(){
_mainAsset.stop()
clearInterval(_mainLoop);
$("#screen").load("/src/views/game.html", function(){
var taikoGame = new Controller(selectedSong, songData, autoPlayEnabled);
taikoGame.run();
});
}
this.playSoundMeka = function(soundID, time){
assets.sounds[soundID + (autoPlayEnabled ? '-meka' : '')].play(time)
}
this.initTiming = function(){
_game.initTiming();
}
this.setHitcircleSpeed = function(speed){
_view.setHitcircleSpeed(speed);
}
this.getHitcircleSpeed = function(){
return _game.getHitcircleSpeed();
}
this.toggleMainMusic = function(){
_game.toggleMainMusic();
}
this.togglePause = function(){
_game.togglePause();
}
this.isPaused = function(){
return _game.isPaused();
}
this.getKeys = function(){
return _keyboard.getKeys();
}
this.setKey = function(keyCode, down){
return _keyboard.setKey(keyCode, down);
}
this.getBindings = function(){
return _keyboard.getBindings();
}
this.getSongData = function(){
return _game.getSongData();
}
this.getEllapsedTime = function(){
return _game.getEllapsedTime();
}
this.getCircles = function(){
return _game.getCircles();
}
this.getCurrentCircle = function(){
return _game.getCurrentCircle();
}
this.updateCurrentCircle = function(){
_game.updateCurrentCircle();
}
this.isWaitingForKeyup = function(key, type){
return _keyboard.isWaitingForKeyup(key, type);
}
this.waitForKeyup = function(key, type){
_keyboard.waitForKeyup(key, type);
}
this.getKeyTime = function(){
return _keyboard.getKeyTime();
}
this.updateCombo = function(score){
_game.updateCombo(score);
}
this.getCombo = function(){
return _game.getCombo();
}
this.getGlobalScore = function(){
return _game.getGlobalScore();
}
this.updateGlobalScore = function(score){
_game.updateGlobalScore(score);
}
this.autoPlay = function(circle){
_mekadon.play(circle)
}
} }

View File

@ -2,7 +2,7 @@ function Game(controller, selectedSong, songData){
var _this = this; var _this = this;
var _selectedSong = selectedSong; var _selectedSong = selectedSong;
var _ellapsedTime; //current time in ms from the beginning of the song this.elapsedTime = {} //current time in ms from the beginning of the song
var _offsetDate; //date when the chrono is started (before the game begins) var _offsetDate; //date when the chrono is started (before the game begins)
var _startDate; //real start date (when the chrono will be 0) var _startDate; //real start date (when the chrono will be 0)
var _currentDate; // refreshed date var _currentDate; // refreshed date
@ -14,60 +14,53 @@ function Game(controller, selectedSong, songData){
var _HPGain= 100/_songData.circles.length; var _HPGain= 100/_songData.circles.length;
var _paused=false; var _paused=false;
var _started=false; var _started=false;
var _mainMusicPlaying=true; var _mainMusicPlaying=false;
var _latestDate; var _latestDate;
var _ellapsedTimeSincePause=0; var _elapsedTimeSincePause=0;
var _musicFadeOut=0; var _musicFadeOut=0;
var _fadeOutStarted=false; var _fadeOutStarted=false;
var _currentTimingPoint=0; var _currentTimingPoint=0;
var _offsetTime=0; var _offsetTime=0;
var _hitcircleSpeed=_songData.difficulty.sliderMultiplier*8; var _hitcircleSpeed=_songData.difficulty.sliderMultiplier*8;
var _timeForDistanceCircle; var _timeForDistanceCircle;
var _mainAsset var _mainAsset
assets.songs.forEach(song => { assets.songs.forEach(song => {
if(song.id == selectedSong.folder){ if(song.id == selectedSong.folder){
_mainAsset = song.sound _mainAsset = song.sound
} }
}) })
this.run = function(){ this.run = function(){
_timeForDistanceCircle=((20*controller.getDistanceForCircle())/_hitcircleSpeed); _timeForDistanceCircle=2500
_this.initTiming(); _this.initTiming();
} }
this.initTiming = function(){ this.initTiming = function(){
_offsetDate = new Date(); _offsetDate = new Date();
_this.setElapsedTime(-_timeForDistanceCircle |0)
_ellapsedTime = { _offsetTime = _timeForDistanceCircle |0
ms:-parseInt(_timeForDistanceCircle),
sec:0,
min:0,
hour:0
}
_offsetTime = parseInt(_timeForDistanceCircle);
_startDate = new Date(); _startDate = new Date();
_startDate.setMilliseconds(_startDate.getMilliseconds()+_offsetTime); //The real start for the game will start when chrono will reach 0 // The real start for the game will start when chrono will reach 0
_startDate.setMilliseconds(_startDate.getMilliseconds()+_offsetTime);
} }
this.update = function(){ this.update = function(){
/* Main operations */ // Main operations
_this.updateTime(); _this.updateTime();
_this.checkTiming(); _this.checkTiming();
_this.updateCirclesStatus(); _this.updateCirclesStatus();
_this.checkPlays(); _this.checkPlays();
/* Event operations */ // Event operations
_this.whenFadeoutMusic(); _this.whenFadeoutMusic();
_this.whenLastCirclePlayed(); _this.whenLastCirclePlayed();
} }
this.getCircles = function(){ this.getCircles = function(){
return _songData.circles; return _songData.circles;
} }
this.updateCirclesStatus = function(){ this.updateCirclesStatus = function(){
@ -77,13 +70,14 @@ function Game(controller, selectedSong, songData){
if(!circle.getPlayed()){ if(!circle.getPlayed()){
var currentTime = _ellapsedTime.ms; var currentTime = _this.getElapsedTime().ms;
var startingTime = circle.getMS()-_timeForDistanceCircle; var startingTime = circle.getMS()-_timeForDistanceCircle;
var finishTime = circle.getMS(); //at circle.getMS(), the cirlce fits the slot // At circle.getMS(), the circle fits the slot
var finishTime = circle.getMS();
if( currentTime >= startingTime && currentTime <= finishTime+200){ if( currentTime >= startingTime && currentTime <= finishTime+200){
if(currentTime>= finishTime-50 && currentTime < finishTime-30){ if(currentTime>= finishTime-50 && currentTime < finishTime-30){
circle.updateStatus(0); circle.updateStatus(0);
} }
else if(currentTime>= finishTime-30 && currentTime < finishTime){ else if(currentTime>= finishTime-30 && currentTime < finishTime){
@ -95,14 +89,20 @@ function Game(controller, selectedSong, songData){
} }
else if(currentTime>finishTime+200 && currentTime<=finishTime+300){ else if(currentTime>finishTime+200 && currentTime<=finishTime+300){
if(controller.multiplayer != 2){
circle.updateStatus(-1); circle.updateStatus(-1);
_currentScore=0; _currentScore=0;
circle.played(_currentScore); circle.played(_currentScore);
controller.displayScore(_currentScore, true); controller.displayScore(_currentScore, true);
_this.updateCurrentCircle(); _this.updateCurrentCircle();
_this.updateCombo(_currentScore); _this.updateCombo(_currentScore);
_this.updateGlobalScore(_currentScore); _this.updateGlobalScore(_currentScore);
}
if(controller.multiplayer == 1){
p2.send("note", {
score: -1
})
}
} }
@ -149,6 +149,12 @@ function Game(controller, selectedSong, songData){
circle.played(score); circle.played(score);
_this.updateCurrentCircle(); _this.updateCurrentCircle();
controller.waitForKeyup(keyCode, "score"); controller.waitForKeyup(keyCode, "score");
if(controller.multiplayer == 1){
p2.send("note", {
score: score,
ms: circle.getMS() - _this.getElapsedTime().ms
})
}
} }
} }
@ -173,25 +179,25 @@ function Game(controller, selectedSong, songData){
break; break;
} }
controller.displayScore(_currentScore); controller.displayScore(_currentScore);
} }
else{ else{
_currentScore=0; _currentScore=0;
controller.displayScore(_currentScore, true); controller.displayScore(_currentScore, true);
} }
_this.updateCombo(_currentScore); _this.updateCombo(_currentScore);
_this.updateGlobalScore(_currentScore); _this.updateGlobalScore(_currentScore);
return _currentScore; return _currentScore;
} }
this.whenLastCirclePlayed = function(){ this.whenLastCirclePlayed = function(){
var circles = _songData.circles; var circles = _songData.circles;
var lastCircle = circles[_songData.circles.length-1]; var lastCircle = circles[_songData.circles.length-1];
if(!_fadeOutStarted && _ellapsedTime.ms>=lastCircle.getMS()+2000){ if(!_fadeOutStarted && _this.getElapsedTime().ms>=lastCircle.getMS()+2000){
_fadeOutStarted=_ellapsedTime.ms _fadeOutStarted=_this.getElapsedTime().ms
} }
} }
@ -199,23 +205,27 @@ function Game(controller, selectedSong, songData){
if(_fadeOutStarted){ if(_fadeOutStarted){
if(_musicFadeOut==0){ if(_musicFadeOut==0){
snd.musicGain.fadeOut(1.6) snd.musicGain.fadeOut(1.6)
_musicFadeOut++
if(controller.multiplayer == 1){
p2.send("gameend")
}
} }
if(_ellapsedTime.ms>=_fadeOutStarted+1600){ if(_musicFadeOut==1 && _this.getElapsedTime().ms>=_fadeOutStarted+1600){
controller.fadeOutOver() controller.fadeOutOver()
_mainAsset.stop() _mainAsset.stop()
_musicFadeOut++
setTimeout(() => { setTimeout(() => {
snd.musicGain.fadeIn() snd.musicGain.fadeIn()
snd.musicGain.unmute() snd.musicGain.unmute()
}, 1000) }, 1000)
} }
_musicFadeOut++;
} }
} }
this.checkTiming = function(){ this.checkTiming = function(){
if(_songData.timingPoints[_currentTimingPoint+1]){ if(_songData.timingPoints[_currentTimingPoint+1]){
if(_this.getEllapsedTime().ms>=_songData.timingPoints[_currentTimingPoint+1].start){ if(_this.getElapsedTime().ms>=_songData.timingPoints[_currentTimingPoint+1].start){
_currentTimingPoint++; _currentTimingPoint++;
} }
} }
@ -225,19 +235,18 @@ function Game(controller, selectedSong, songData){
return _songData.timingPoints[_currentTimingPoint]; return _songData.timingPoints[_currentTimingPoint];
} }
this.toggleMainMusic = function(){ this.playMainMusic = function(){
if(_mainMusicPlaying){ var ms = _this.getElapsedTime().ms
_mainAsset.stop(); if(!_mainMusicPlaying && (!_fadeOutStarted || ms<_fadeOutStarted+1600)){
_mainMusicPlaying=false; if(controller.multiplayer != 2){
} _mainAsset.play((ms < 0 ? -ms : 0) / 1000, false, Math.max(0, ms / 1000));
else{ }
_mainAsset.play(0, false, _this.getEllapsedTime().ms / 1000);
_mainMusicPlaying=true; _mainMusicPlaying=true;
} }
} }
this.fadeOutOver = function(){ this.fadeOutOver = function(){
_fadeOutStarted=false;
} }
this.getHitcircleSpeed = function(){ this.getHitcircleSpeed = function(){
@ -249,15 +258,15 @@ function Game(controller, selectedSong, songData){
assets.sounds["pause"].play(); assets.sounds["pause"].play();
_paused=true; _paused=true;
_latestDate = new Date(); _latestDate = new Date();
_this.toggleMainMusic(); _mainAsset.stop();
_mainMusicPlaying=false;
} }
else{ else{
assets.sounds["cancel"].play(); assets.sounds["cancel"].play();
_paused=false; _paused=false;
var currentDate = new Date(); var currentDate = new Date();
_ellapsedTimeSincePause = _ellapsedTimeSincePause + Math.abs(currentDate.getTime() - _latestDate.getTime()); _elapsedTimeSincePause = _elapsedTimeSincePause + currentDate.getTime() - _latestDate.getTime();
_this.toggleMainMusic();
} }
} }
@ -265,30 +274,33 @@ function Game(controller, selectedSong, songData){
return _paused; return _paused;
} }
this.getEllapsedTime = function(){ this.getElapsedTime = function(){
return _ellapsedTime; return this.elapsedTime;
}
this.setElapsedTime = function(time){
this.elapsedTime.ms = time
this.elapsedTime.sec = (this.elapsedTime.ms / 1000 |0) % 60
this.elapsedTime.min = (this.elapsedTime.ms / 1000 / 60 |0) % 60
this.elapsedTime.hour = (this.elapsedTime.ms / 1000 / 60 / 60 |0) % 60
} }
this.updateTime = function(){ this.updateTime = function(){
_currentDate = new Date(); _currentDate = new Date();
var time = _this.getElapsedTime()
if(_ellapsedTime.ms<0){ if(time.ms<0){
_ellapsedTime.ms = _currentDate.getTime() - _startDate.getTime(); _this.setElapsedTime(_currentDate.getTime() - _startDate.getTime() - _elapsedTimeSincePause)
} }
else if(_ellapsedTime.ms>=0 && !_started){ else if(time.ms>=0 && !_started){
_startDate = new Date(); _startDate = new Date();
_ellapsedTime.ms = Math.abs(_startDate.getTime() - _currentDate.getTime()); _elapsedTimeSincePause = 0;
_this.setElapsedTime(_currentDate.getTime() - _startDate.getTime())
_started=true; _started=true;
} }
else if(_ellapsedTime.ms>=0 && _started){ else if(time.ms>=0 && _started){
_ellapsedTime.ms = Math.abs(_startDate.getTime() - _currentDate.getTime()) - _ellapsedTimeSincePause; _this.setElapsedTime(_currentDate.getTime() - _startDate.getTime() - _elapsedTimeSincePause)
} }
_ellapsedTime.sec = parseInt(_ellapsedTime.ms / 1000) % 60;
_ellapsedTime.min = parseInt(_ellapsedTime.ms / (1000 * 60)) % 60;
_ellapsedTime.hour = parseInt(_ellapsedTime.ms / (1000 * 60 * 60)) % 60;
} }
this.getCircles = function(){ this.getCircles = function(){
@ -400,32 +412,32 @@ function Game(controller, selectedSong, songData){
if(_combo>=11 && _combo<=20){ if(_combo>=11 && _combo<=20){
score+=100; score+=100;
} }
else if(_combo>=21 && _combo<=30){ else if(_combo>=21 && _combo<=30){
score+=200; score+=200;
} }
else if(_combo>=31 && _combo<=40){ else if(_combo>=31 && _combo<=40){
score+=300; score+=300;
} }
else if(_combo>=41 && _combo<=50){ else if(_combo>=41 && _combo<=50){
score+=400; score+=400;
} }
else if(_combo>=51 && _combo<=60){ else if(_combo>=51 && _combo<=60){
score+=500; score+=500;
} }
else if(_combo>=61 && _combo<=70){ else if(_combo>=61 && _combo<=70){
score+=500; score+=500;
} }
else if(_combo>=71 && _combo<=80){ else if(_combo>=71 && _combo<=80){
score+=600; score+=600;
} }
else if(_combo>=81 && _combo<=90){ else if(_combo>=81 && _combo<=90){
score+=700; score+=700;
} }
else if(_combo>=91 && _combo<=100){ else if(_combo>=91 && _combo<=100){
score+=800; score+=800;
} }
_globalScore.points+=score; _globalScore.points+=score;
} }

View File

@ -65,14 +65,21 @@ function Keyboard(controller){
} }
this.checkMenuKeys = function(){ this.checkMenuKeys = function(){
_gamepad.play(1) if(!controller.multiplayer){
_this.checkKey(_kbd["back"], "menu", function(){ _gamepad.play(1)
controller.togglePause(); _this.checkKey(_kbd["pause"], "menu", function(){
controller.songSelection(); controller.togglePauseMenu();
}) })
_this.checkKey(_kbd["pause"], "menu", function(){ }
controller.togglePauseMenu(); if(controller.multiplayer != 2){
}) _this.checkKey(_kbd["back"], "menu", function(){
if(controller.multiplayer == 1){
p2.send("gameend")
}
controller.togglePause();
controller.songSelection();
})
}
} }
this.checkKey = function(keyCode, keyup, callback){ this.checkKey = function(keyCode, keyup, callback){
@ -85,7 +92,7 @@ function Keyboard(controller){
this.checkKeySound = function(keyCode, sound){ this.checkKeySound = function(keyCode, sound){
_this.checkKey(keyCode, "sound", function(){ _this.checkKey(keyCode, "sound", function(){
assets.sounds["note_"+sound].play() assets.sounds["note_"+sound].play()
_keyTime[sound] = controller.getEllapsedTime().ms _keyTime[sound] = controller.getElapsedTime().ms
}) })
} }

View File

@ -51,6 +51,8 @@ class Loader{
})) }))
}) })
p2 = new P2Connection()
this.promises.push(ajax("/api/songs").then(songs => { this.promises.push(ajax("/api/songs").then(songs => {
assets.songs = JSON.parse(songs) assets.songs = JSON.parse(songs)
})) }))
@ -100,4 +102,5 @@ function promiseLoad(asset){
} }
var snd = {} var snd = {}
var p2
new Loader() new Loader()

View File

@ -1,7 +1,9 @@
class loadSong{ class loadSong{
constructor(selectedSong, autoPlayEnabled){ constructor(selectedSong, autoPlayEnabled, multiplayer){
this.selectedSong = selectedSong this.selectedSong = selectedSong
this.multiplayer = multiplayer
this.autoPlayEnabled = autoPlayEnabled this.autoPlayEnabled = autoPlayEnabled
this.diff = this.selectedSong.difficulty.slice(0, -4)
this.songFilePath = "/songs/" + this.selectedSong.folder + "/" + this.selectedSong.difficulty this.songFilePath = "/songs/" + this.selectedSong.folder + "/" + this.selectedSong.difficulty
$("#screen").load("/src/views/loadsong.html", () => { $("#screen").load("/src/views/loadsong.html", () => {
this.run() this.run()
@ -42,11 +44,46 @@ class loadSong{
Promise.all(promises).then(() => { Promise.all(promises).then(() => {
$("#screen").load("/src/views/game.html", () => { $("#screen").load("/src/views/game.html", () => {
var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled) this.setupMultiplayer()
taikoGame.run()
}) })
}, () => { }, error => {
console.error(error)
alert("An error occurred, please refresh") alert("An error occurred, please refresh")
}) })
} }
} setupMultiplayer(){
if(this.multiplayer){
this.song2Data = this.songData
this.selectedSong2 = this.selectedSong
p2.onmessage("gamestart", () => {
var taikoGame1 = new Controller(this.selectedSong, this.songData, false, 1)
var taikoGame2 = new Controller(this.selectedSong2, this.song2Data, true, 2)
taikoGame1.run(taikoGame2)
}, true)
p2.onmessage("gameload", response => {
if(response == this.diff){
p2.send("gamestart")
}else{
this.selectedSong2 = {
title: this.selectedSong.title,
folder: this.selectedSong.folder,
difficulty: response + ".osu"
}
ajax("/songs/" + this.selectedSong2.folder + "/" + this.selectedSong2.difficulty).then(data => {
this.song2Data = data.replace(/\0/g, "").split("\n")
p2.send("gamestart")
}, () => {
p2.send("gamestart")
})
}
}, true)
p2.send("join", {
id: this.selectedSong.folder,
diff: this.diff
})
}else{
var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled)
taikoGame.run()
}
}
}

View File

@ -7,27 +7,56 @@ class Mekadon{
} }
play(circle){ play(circle){
if(circle.getStatus() == 450){ if(circle.getStatus() == 450){
var kbd = this.controller.getBindings() return this.playNow(circle)
if(circle.getType() == "don"){
this.setKey(this.lr ? kbd["don_l"] : kbd["don_r"])
this.lr = !this.lr
}else if(circle.getType() == "daiDon"){
this.setKey(kbd["don_l"])
this.setKey(kbd["don_r"])
this.lr = false
}else if(circle.getType() == "ka"){
this.setKey(this.lr ? kbd["ka_l"] : kbd["ka_r"])
this.lr = !this.lr
}else if(circle.getType() == "daiKa"){
this.setKey(kbd["ka_l"])
this.setKey(kbd["ka_r"])
this.lr = false
}
var score = this.game.checkScore(circle);
circle.played(score);
this.game.updateCurrentCircle();
} }
} }
playAt(circle, ms, score){
var currentMs = circle.getMS() - this.controller.getElapsedTime().ms
if(ms > currentMs - 10){
return this.playNow(circle, score)
}
}
miss(circle){
var currentMs = circle.getMS() - this.controller.getElapsedTime().ms
if(0 > currentMs - 10){
circle.updateStatus(-1)
circle.played(0)
this.controller.displayScore(0, true)
this.game.updateCurrentCircle()
this.game.updateCombo(0)
this.game.updateGlobalScore(0)
return true
}
}
playNow(circle, score){
var kbd = this.controller.getBindings()
if(circle.getType() == "don"){
this.setKey(this.lr ? kbd["don_l"] : kbd["don_r"])
this.lr = !this.lr
}else if(circle.getType() == "daiDon"){
this.setKey(kbd["don_l"])
this.setKey(kbd["don_r"])
this.lr = false
}else if(circle.getType() == "ka"){
this.setKey(this.lr ? kbd["ka_l"] : kbd["ka_r"])
this.lr = !this.lr
}else if(circle.getType() == "daiKa"){
this.setKey(kbd["ka_l"])
this.setKey(kbd["ka_r"])
this.lr = false
}
if(typeof score == "undefined"){
score = this.game.checkScore(circle)
}else{
this.controller.displayScore(score)
this.game.updateCombo(score)
this.game.updateGlobalScore(score)
}
circle.updateStatus(score)
circle.played(score)
this.game.updateCurrentCircle()
return true
}
setKey(keyCode){ setKey(keyCode){
var self = this var self = this
if(this.keys[keyCode]){ if(this.keys[keyCode]){

132
public/src/js/p2.js Normal file
View File

@ -0,0 +1,132 @@
class P2Connection{
constructor(){
this.closed = true
this.lastMessages = {}
this.msgCallbacks = {}
this.closeCallbacks = new Set()
this.openCallbacks = new Set()
this.notes = []
this.otherConnected = false
this.onmessage("gamestart", () => {
this.otherConnected = true
this.notes = []
})
this.onmessage("gameend", () => {
this.otherConnected = false
})
this.onmessage("note", response => {
this.notes.push(response)
})
}
open(){
this.closed = false
var wsProtocol = location.protocol == "https:" ? "wss:" : "ws:"
this.socket = new WebSocket(wsProtocol + "//" + location.host + "/p2")
var events = ["open", "close", "message"]
events.forEach(eventName => {
this.socket.addEventListener(eventName, event => {
this[eventName + "Event"](event)
})
})
}
openEvent(event){
this.openCallbacks.forEach(obj => {
obj.callback()
if(obj.once){
this.openCallbacks.delete(obj)
}
})
}
onopen(callback, once){
this.openCallbacks.add({
callback: callback,
once: once
})
}
close(){
this.closed = true
this.socket.close()
}
closeEvent(event){
if(!this.closed){
setTimeout(() => {
if(this.socket.readyState != this.socket.OPEN){
this.open()
}
}, 500)
}
this.closeCallbacks.forEach(obj => {
obj.callback()
if(obj.once){
this.closeCallbacks.delete(obj)
}
})
}
onclose(callback, once){
this.closeCallbacks.add({
callback: callback,
once: once
})
}
send(type, value){
if(this.socket.readyState == this.socket.OPEN){
if(typeof value == "undefined"){
this.socket.send(JSON.stringify({type: type}))
}else{
this.socket.send(JSON.stringify({type: type, value: value}))
}
}else{
this.onopen(() => {
this.send(type, value)
}, true)
}
}
messageEvent(event){
try{
var data = JSON.parse(event.data)
}catch(e){
var data = {}
}
this.lastMessages[data.type] = data.value
if(this.msgCallbacks[data.type]){
this.msgCallbacks[data.type].forEach(obj => {
obj.callback(data.value)
if(obj.once){
delete this.msgCallbacks[obj]
}
})
}
}
onmessage(type, callback, once){
if(!(type in this.msgCallbacks)){
this.msgCallbacks[type] = new Set()
}
this.msgCallbacks[type].add({
callback: callback,
once: once
})
}
getMessage(type, callback){
if(type in this.lastMessages){
callback(this.lastMessages[type])
}
}
play(circle, mekadon){
if(this.otherConnected){
if(this.notes.length == 0){
mekadon.play(circle)
}else{
var note = this.notes[0]
if(note.score >= 0){
if(mekadon.playAt(circle, note.ms, note.score)){
this.notes.shift()
}
}else{
if(mekadon.miss(circle)){
this.notes.shift()
}
}
}
}
}
}

View File

@ -56,6 +56,7 @@ function SongSelect(){
this.run = function(){ this.run = function(){
_this.createCode(); _this.createCode();
_this.startP2();
$("#song-container").show(); $("#song-container").show();
@ -84,7 +85,7 @@ function SongSelect(){
_selectedSong.folder = songID; _selectedSong.folder = songID;
snd.musicGain.fadeIn() snd.musicGain.fadeIn()
new loadSong(_selectedSong, e.shiftKey); new loadSong(_selectedSong, e.shiftKey, e.ctrlKey);
}); });
$(".song").hover(function(){ $(".song").hover(function(){
@ -236,6 +237,41 @@ function SongSelect(){
$('.difficulty').hide(); $('.difficulty').hide();
} }
this.onusers = function(response){
var oldP2Elements = document.getElementsByClassName("p2")
for(var i = oldP2Elements.length; i--;){
oldP2Elements[i].classList.remove("p2")
}
if(response){
response.forEach(idDiff => {
id = idDiff.id |0
diff = idDiff.diff
if(diff in _diffNames){
var idElement = document.getElementById("song-" + id)
if(idElement){
idElement.classList.add("p2")
var diffElement = idElement.getElementsByClassName("difficulty " + diff)[0]
if(diffElement){
diffElement.classList.add("p2")
}
}
}
})
}
}
this.startP2 = function(){
p2.getMessage("users", response =>{
this.onusers(response)
})
p2.onmessage("users", response => {
this.onusers(response)
})
if(p2.closed){
p2.open()
}
}
$("#screen").load("/src/views/songselect.html", _this.run); $("#screen").load("/src/views/songselect.html", _this.run);
} }

View File

@ -4,7 +4,14 @@ class View{
this.bg = bg this.bg = bg
this.diff = diff this.diff = diff
this.canvas = new ScalableCanvas("canvas", $(window).width(), $(window).height()) if(this.controller.multiplayer == 2){
this.canvas = new ScalableCanvas("canvas-p2", $(window).width(), $(window).height() / 3 * 2)
this.canvas.canvas.style.position = "absolute"
this.canvas.canvas.style.top = "33%"
document.getElementById("game").appendChild(this.canvas.canvas)
}else{
this.canvas = new ScalableCanvas("canvas", $(window).width(), $(window).height())
}
this.winW = this.canvas.scaledWidth this.winW = this.canvas.scaledWidth
this.winH = this.canvas.scaledHeight this.winH = this.canvas.scaledHeight
this.ctx = this.canvas.ctx this.ctx = this.canvas.ctx
@ -50,9 +57,16 @@ class View{
positionning(){ positionning(){
this.canvas.rescale() this.canvas.rescale()
this.canvas.resize($(window).width(), $(window).height()) var height = $(window).height()
if(this.controller.multiplayer == 2){
height = height / 3 * 2
}
this.canvas.resize($(window).width(), height)
this.winW = this.canvas.scaledWidth this.winW = this.canvas.scaledWidth
this.winH = this.canvas.scaledHeight this.winH = this.canvas.scaledHeight
if(this.controller.multiplayer == 2){
this.winH = this.winH / 2 * 3
}
this.barY = 0.25 * this.winH this.barY = 0.25 * this.winH
this.barH = 0.23 * this.winH this.barH = 0.23 * this.winH
@ -113,7 +127,7 @@ class View{
} }
updateDonFaces(){ updateDonFaces(){
if(this.controller.getEllapsedTime().ms >= this.nextBeat){ if(this.controller.getElapsedTime().ms >= this.nextBeat){
this.nextBeat += this.controller.getSongData().beatInfo.beatInterval this.nextBeat += this.controller.getSongData().beatInfo.beatInterval
if(this.controller.getCombo() >= 50){ if(this.controller.getCombo() >= 50){
this.currentBigDonFace = (this.currentBigDonFace + 1) % 2 this.currentBigDonFace = (this.currentBigDonFace + 1) % 2
@ -190,7 +204,7 @@ class View{
drawMeasures(){ drawMeasures(){
var measures = this.controller.getSongData().measures var measures = this.controller.getSongData().measures
var currentTime = this.controller.getEllapsedTime().ms var currentTime = this.controller.getElapsedTime().ms
measures.forEach((measure, index)=>{ measures.forEach((measure, index)=>{
var timeForDistance = 70 / this.circleSize * this.distanceForCircle / measure.speed var timeForDistance = 70 / this.circleSize * this.distanceForCircle / measure.speed
@ -206,7 +220,7 @@ class View{
drawMeasure(measure){ drawMeasure(measure){
var z = this.canvas.scale var z = this.canvas.scale
var currentTime = this.controller.getEllapsedTime().ms var currentTime = this.controller.getElapsedTime().ms
var measureX = this.slotX + measure.speed / (70 / this.circleSize) * (measure.ms - currentTime) var measureX = this.slotX + measure.speed / (70 / this.circleSize) * (measure.ms - currentTime)
this.ctx.strokeStyle = "#bab8b8" this.ctx.strokeStyle = "#bab8b8"
this.ctx.lineWidth = 2 this.ctx.lineWidth = 2
@ -388,7 +402,7 @@ class View{
for(var i = circles.length; i--;){ for(var i = circles.length; i--;){
var circle = circles[i] var circle = circles[i]
var currentTime = this.controller.getEllapsedTime().ms var currentTime = this.controller.getElapsedTime().ms
var timeForDistance = 70 / this.circleSize * this.distanceForCircle / circle.getSpeed() + this.bigCircleSize / 2 var timeForDistance = 70 / this.circleSize * this.distanceForCircle / circle.getSpeed() + this.bigCircleSize / 2
var startingTime = circle.getMS() - timeForDistance var startingTime = circle.getMS() - timeForDistance
// At circle.getMS(), the cirlce fits the slot // At circle.getMS(), the cirlce fits the slot
@ -459,7 +473,7 @@ class View{
var fill, size, faceID var fill, size, faceID
if(!circlePos){ if(!circlePos){
var currentTime = this.controller.getEllapsedTime().ms var currentTime = this.controller.getElapsedTime().ms
circlePos = { circlePos = {
x: this.slotX + circle.getSpeed() / (70 / this.circleSize) * (circle.getMS() - currentTime), x: this.slotX + circle.getSpeed() / (70 / this.circleSize) * (circle.getMS() - currentTime),
y: this.circleY y: this.circleY
@ -549,7 +563,7 @@ class View{
drawTime(){ drawTime(){
var z = this.canvas.scale var z = this.canvas.scale
var time = this.controller.getEllapsedTime() var time = this.controller.getElapsedTime()
this.ctx.globalAlpha = 0.7 this.ctx.globalAlpha = 0.7
this.ctx.fillStyle = "#000" this.ctx.fillStyle = "#000"
@ -579,7 +593,7 @@ class View{
this.ctx.closePath() this.ctx.closePath()
this.ctx.fill() this.ctx.fill()
var currentTime = this.controller.getEllapsedTime().ms var currentTime = this.controller.getElapsedTime().ms
var keyTime = this.controller.getKeyTime() var keyTime = this.controller.getKeyTime()
var sound = keyTime["don"] > keyTime["ka"] ? "don" : "ka" var sound = keyTime["don"] > keyTime["ka"] ? "don" : "ka"
if(keyTime[sound] > currentTime - 200){ if(keyTime[sound] > currentTime - 200){

168
server.py Normal file
View File

@ -0,0 +1,168 @@
#!/usr/bin/env python
import asyncio
import websockets
import json
users = []
server_status = {
"waiting": {}
}
def msgobj(type, value=None):
if value == None:
return json.dumps({"type": type})
else:
return json.dumps({"type": type, "value": value})
def status_event():
value = []
for id, userDiff in server_status["waiting"].items():
value.append({
"id": id,
"diff": userDiff["diff"]
})
return msgobj("users", value)
async def notify_status():
ready_users = [user for user in users if "ws" in user and user["action"] == "ready"]
if ready_users:
sent_msg = status_event()
await asyncio.wait([user["ws"].send(sent_msg) for user in ready_users])
async def connection(ws, path):
# User connected
user = {
"ws": ws,
"action": "ready"
}
users.append(user)
try:
# Notify user about other users
await ws.send(status_event())
while True:
try:
message = await asyncio.wait_for(ws.recv(), timeout=5)
except asyncio.TimeoutError:
# Keep user connected
pong_waiter = await ws.ping()
try:
await asyncio.wait_for(pong_waiter, timeout=5)
except asyncio.TimeoutError:
# Disconnect
break
else:
# Message received
try:
data = json.loads(message)
except json.decoder.JSONDecodeError:
data = {}
action = user["action"]
type = data["type"] if "type" in data else None
value = data["value"] if "value" in data else None
if action == "ready":
# Not playing or waiting
if type == "join":
waiting = server_status["waiting"]
id = value["id"] if "id" in value else None
diff = value["diff"] if "diff" in value else None
if not id or not diff:
continue
if id not in waiting:
# Wait for another user
user["action"] = "waiting"
user["gameid"] = id
waiting[id] = {
"user": user,
"diff": diff
}
await ws.send(msgobj("waiting"))
else:
# Join the other user and start game
user["other_user"] = waiting[id]["user"]
waiting_diff = waiting[id]["diff"]
del waiting[id]
if "ws" in user["other_user"]:
user["action"] = "loading"
user["other_user"]["action"] = "loading"
user["other_user"]["other_user"] = user
await asyncio.wait([
ws.send(msgobj("gameload", waiting_diff)),
user["other_user"]["ws"].send(msgobj("gameload", diff))
])
else:
# Wait for another user
user["action"] = "waiting"
user["gameid"] = id
waiting[id] = {
"user": user,
"diff": diff
}
await ws.send(msgobj("waiting"))
# Update others on waiting players
await notify_status()
elif action == "waiting" or action == "loading" or action == "loaded":
# Waiting for another user
if type == "leave":
# Stop waiting
del server_status["waiting"][user["gameid"]]
del user["gameid"]
user["action"] = "ready"
await asyncio.wait([
ws.send(msgobj("left")),
notify_status()
])
if action == "loading":
if type == "gamestart":
user["action"] = "loaded"
if user["other_user"]["action"] == "loaded":
user["action"] = "playing"
user["other_user"]["action"] = "playing"
sent_msg = msgobj("gamestart")
await asyncio.wait([
ws.send(sent_msg),
user["other_user"]["ws"].send(sent_msg)
])
elif action == "playing":
# Playing with another user
if "other_user" in user and "ws" in user["other_user"]:
if type == "note":
await user["other_user"]["ws"].send(msgobj("note", value))
if type == "gameend":
# User wants to disconnect
user["action"] = "ready"
user["other_user"]["action"] = "ready"
sent_msg1 = msgobj("gameend")
sent_msg2 = status_event()
await asyncio.wait([
ws.send(sent_msg1),
ws.send(sent_msg2),
user["other_user"]["ws"].send(sent_msg1),
user["other_user"]["ws"].send(sent_msg2)
])
del user["other_user"]
else:
# Other user disconnected
user["action"] = "ready"
await asyncio.wait([
ws.send(msgobj("gameend")),
ws.send(status_event())
])
finally:
# User disconnected
del user["ws"]
del users[users.index(user)]
if "other_user" in user and "ws" in user["other_user"]:
user["other_user"]["action"] = "ready"
await asyncio.wait([
user["other_user"]["ws"].send(msgobj("gameend")),
user["other_user"]["ws"].send(status_event())
])
if user["action"] == "waiting":
del server_status["waiting"][user["gameid"]]
await notify_status()
asyncio.get_event_loop().run_until_complete(
websockets.serve(connection, "localhost", 34802)
)
asyncio.get_event_loop().run_forever()