/* this is a partually port from as3 so this is a mess */
Game=
{
Rally:
{
Ball:
{
BallCollisions: {}, BallEval: {}
},
Player:
{
PlayerCollisions: {}, PlayerEvalA: {}, PlayerEvalXY: {}, KeyHandler: {}
},
Referee: {},
Scale: {},
Time: {},
Timer: {},
BorderLines: {},
FieldLines: {},
ServeLines: {},
MiddleLines: {MiddleLine: {}}
},
Referee: {},
Wait: {},
};
NetworkClient={};
document.addEventListener('DOMContentLoaded', function()
{
window.addEventListener('resize', resize);
resize();
keySpaceOccupied=false;
chat=(function()
{
var mesI=0;
var listNode=document.querySelector('#chat ._list');
var inputNode=document.querySelector('#chat input');
var mesTemplateNode=document.querySelector('templates > .chat_message');
inputNode.addEventListener('focusin', function()
{
keySpaceOccupied=true;
});
inputNode.addEventListener('focusout', function()
{
keySpaceOccupied=false;
});
inputNode.addEventListener('keydown', function(event)
{
if(event.keyCode==13)
{
networkClient.messageSend({tp: 'chat', text: inputNode.value});
inputNode.value='';
}
});
return {
receive: function(mes)
{
var mesNode=mesTemplateNode.cloneNode(true);
mesNode.querySelector('._name').innerText=mes.name;
mesNode.querySelector('._text').innerText=mes.text;
listNode.prepend(mesNode);
//listNode.scrollTo(0, listNode.scrollHeight);
if(mesI==30)
{
//listNode.childNodes[0].remove();
var m=listNode.childNodes;
m[m.length-1].remove();
}
else
{
mesI++;
}
}
}
})();
advice=(function()
{
var node=document.querySelector('#wait_advice');
var advices=['Совет: набегайте на мяч под углом, чтобы придать ему вращение.', 'Совет: если мяч от соперника летит прямиком в аут, не отбивайте его.', 'Совет: набегайте на мяч, чтобы увеличить его скорость.', 'Совет: следите за вращением мяча, чтобы предугадывать его траекторию.'];
return {
hide: function()
{
node.style.display='none';
},
refresh: function()
{
node.style.display='block';
node.innerText=advices[parseInt(Math.min(advices.length*Math.random(), advices.length-1))];
}
}
})();
waitView=(function()
{
var waitNode=document.getElementById('wait');
var waitReadyNode=document.getElementById('wait_ready');
return {
status: function(status)
{
if(! status)
{
waitNode.classList.remove('success');
waitNode.classList.remove('fail');
}
else if(status=='success')
{
waitNode.classList.add('success');
waitNode.classList.remove('fail');
}
else
{
waitNode.classList.remove('success');
waitNode.classList.add('fail');
}
},
ready: function(text)
{
waitReadyNode.innerHTML=text;
waitNode.style.display='flex';
},
hide: function()
{
waitNode.style.display='none';
document.getElementById('teaching_fail_border').style.display='none';
document.getElementById('teaching_fail_out').style.display='none';
},
show: function()
{
waitNode.style.display='flex';
}
}
})();
lobby=(function()
{
return {
hide: function()
{
document.body.classList.add('lobby_closed');
},
show: function()
{
document.body.classList.remove('lobby_closed');
}
};
})();
teachingStage2=true;
teaching=(function()
{
var playerShadow=document.getElementById('player_shadow');
var ballShadow=document.getElementById('ball_shadow');
var animateInterval=false;
var animateTimeout=false;
var animateStopped=false;
var playerShadowAnimateStop=function()
{
animateStopped=true;
if(animateInterval)
{
clearInterval(animateInterval);
}
if(animateTimeout)
{
clearTimeout(animateTimeout);
}
animateInterval=false;
animateTimeout=false;
playerShadow.style.display='none';
ballShadow.style.display='none';
}
playerShadowAnimate=function(n, x0, pvx, pvy, ba, pa=0, bv=50)
{
if(n==0 || animateStopped)
{
animateTimeout=false;
return;
}
animateStopped=false;
/*playerShadow.style.marginTop='-50px';
ballShadow.style.marginLeft='-150px';
playerShadow.style.display='block';
ballShadow.style.display='block';*/
/* var px0=x0-pvx;
var py0=x0-pvy;
var bx0=x0+bv;*/
var baa=Math.sqrt(1-ba*ba);
var i=-1;
var m=animateInterval=setInterval(function()
{
playerShadow.style.marginTop=parseInt(i*pvy)+'px';
playerShadow.style.marginLeft=parseInt(x0+i*pvx)+'px';
playerShadow.style.transform='rotate('+pa+'deg)';
if(i<=0)
{
ballShadow.style.marginLeft=parseInt(x0+i*bv-9)+'px';
ballShadow.style.marginTop='-4px';
}
else
{
ballShadow.style.marginLeft=parseInt(x0-i*bv*baa-9)+'px';
ballShadow.style.marginTop=parseInt(i*bv*ba-4)+'px';
}
if(i==-1)
{
playerShadow.style.display='block';
ballShadow.style.display='block'
}
i+=0.007;
if(i>1)
{
clearInterval(m);
animateInterval=false;
playerShadow.style.display='none';
ballShadow.style.display='none';
//if(! animateStopped)
animateTimeout=setTimeout(function(){playerShadowAnimate(n-1, x0, pvx, pvy, ba, pa, bv);}, 1500/*2000*/);
}
}, 0);
}
animate4=function()
{
animateStopped=false;
setTimeout(function(){playerShadowAnimate(3, 331, -20, 0, 0.406, 78,120);}, 500);
}
animate5=function()
{
animateStopped=false;
setTimeout(function(){playerShadowAnimate(3, 331, -20, 0, -0.406, 102,120);}, 500);
}
animate6=function()
{
animateStopped=false;
setTimeout(function(){playerShadowAnimate(3, 331, -20, -40, 0, 90,120);}, 500);
}
animate7=function()
{
animateStopped=false;
setTimeout(function(){playerShadowAnimate(3, 331, -20, -40, 0.4, 78,120);}, 500);
}
var nodeBlock2=document.getElementById('teaching_block2');
var nodeBlock2_2=document.getElementById('teaching_block2_2');
return {
stage: false,
start: function()
{
if(localStorage.teachingStage)
{
teaching['step'+localStorage.teachingStage]();
return;
}
teaching.stage=0;
Main.game.rally.player0.holded=false;
//if(localStorage.teachingEnd) return;
//stage=0;
/*var m=false;
setTimeout(this.step1, 2000);
document.body.addEventListener('keydown', m=function(e)
{
if(e.keyCode==32)
{
document.body.removeEventListener('keydown', m);
teaching.step2();
}
});*/
document.getElementById('border_serve').style.display='none';
setTimeout(function(){teaching.step1();}, 1000);
},
/*step1: function()
{
if(stage>1) return;
document.getElementById('teaching_block1').style.display='block';
},*/
step1: function()
{
//if(ym) ym(64682380,'reachGoal','ts1');
var keysMove=[65, 68, 83, 87];
//var keysTurn=[39, 37];
var keysMoveWas=false;
var keysTurnWas=false;
var m=false;
teaching.stage=2;
teachingStage2=true;
document.getElementById('teaching_block1').style.display='none';
document.getElementById('teaching_block2').style.display='block';
document.body.addEventListener('keydown', m=function(e)
{
if(keysMove.indexOf(e.keyCode)!==-1)
{
document.body.removeEventListener('keydown', m);
setTimeout(function(){teaching.step2();}, 1000);
}
});
},
step2: function()
{
if(ym) ym(64682380,'reachGoal','ts1');
teaching.stage=2;
localStorage.teachingStage=teaching.stage;
teachingStage2=true;
document.getElementById('teaching_block2').style.display='none';
document.getElementById('teaching_block2_2').style.display='block';
var keysTurn=[39, 37];
var m=false;
document.body.addEventListener('keydown', m=function(e)
{
if(keysTurn.indexOf(e.keyCode)!==-1)
{
document.body.removeEventListener('keydown', m);
setTimeout(function(){teaching.step3();}, 1000);
}
});
},
step3: function()
{
if(ym) ym(64682380,'reachGoal','ts2');
teaching.stage=3;
localStorage.teachingStage=teaching.stage;
teachingStage2=false;
//document.getElementById('teaching_block2').style.display='none';
document.getElementById('teaching_block2_2').style.display='none';
document.getElementById('wait_').style.display='block';
document.getElementById('teaching_block1').style.display='block';
//document.getElementById('teaching_block4').style.display='block';
var m=false;
document.body.addEventListener('keydown', m=function(e)
{
if(e.keyCode==32)
{
Main.game.rally.player0.hold(true, Main.game.rally.time.get())
Main.game.rally.player0.holded=false;
document.body.removeEventListener('keydown', m);
//teaching.step4();
}
});
/*teaching.end();*/
},
step4: function()
{
if(ym) ym(64682380,'reachGoal','ts3');
teaching.stage=4;
animate4();
localStorage.teachingStage=teaching.stage;
document.getElementById('wait_').style.display='block';
document.getElementById('teaching_block1').style.display='none';
document.getElementById('teaching_block4').style.display='block';
document.getElementById('border_cort_blue_bottom').style.display='block';
},
step5: function()
{
if(ym) ym(64682380,'reachGoal','ts4');
teaching.stage=5;
animate5();
localStorage.teachingStage=teaching.stage;
document.getElementById('wait_').style.display='block';
document.getElementById('teaching_block4').style.display='none';
document.getElementById('teaching_block5').style.display='block';
document.getElementById('border_cort_blue_top').style.display='block';
document.getElementById('border_cort_blue_bottom').style.display='none';
},
step6: function()
{
Main.game.rally.player0.afterKeyPressedChange();
if(ym) ym(64682380,'reachGoal','ts5');
teaching.stage=6;
animate6();
localStorage.teachingStage=teaching.stage;
document.getElementById('wait_').style.display='block';
document.getElementById('border_cort_blue_top').style.display='none';
document.getElementById('teaching_block5').style.display='none';
document.getElementById('teaching_block2').style.display='block';
document.getElementById('teaching_block2_cont').innerHTML='Продолжаем! Набегайте на мяч снизу вверх,
чтобы закрутить его.';
teachingStage2=true;
},
step7: function()
{
if(ym) ym(64682380,'reachGoal','ts6');
teaching.stage=7;
animate7();
localStorage.teachingStage=teaching.stage;
document.getElementById('wait_').style.display='block';
document.getElementById('teaching_block4').style.display='block';
document.getElementById('teaching_block2').style.display='none';
document.getElementById('teaching_block4_cont').innerHTML='Супер! Теперь отбейте мяч сюда,
закрутив его посильнее.';
document.getElementById('border_cort_blue_bottom').style.display='block';
},
step11: function()
{
if(ym) ym(64682380,'reachGoal','ts7');
teaching.stage=11;
localStorage.teachingStage=teaching.stage;
teaching.stage11successCount=0;
document.getElementById('wait_').style.display='block';
document.getElementById('teaching_block4_cont').innerHTML='...или сюда.';
document.getElementById('teaching_block5_cont').innerHTML='Теперь подаёт соперник! Отбейте мяч сюда...';
document.getElementById('border_cort_blue_bottom').style.display='block';
document.getElementById('teaching_block5').style.display='block';
document.getElementById('teaching_block4').style.display='block';
document.getElementById('border_cort_blue_top').style.display='block';
},
step12: function()
{
if(ym) ym(64682380,'reachGoal','ts11');
teaching.stage=12;
localStorage.teachingEnd=1;
document.getElementById('wait_').style.display='none';
document.getElementById('teaching_block4').style.display='none';
document.getElementById('teaching_block5').style.display='none';
document.getElementById('border_cort_blue_top').style.display='none';
document.getElementById('border_cort_blue_bottom').style.display='none';
toast('Поздравляем! Вы прошли обучение.
Пора в бой.');
keySpaceOccupied=true;
setTimeout(function(){localStorage.teachingStage='';window.location.href='/?teaching_end';}, 1900);
},
end: function()
{
stage=3;
teachingStage2=false;
document.getElementById('teaching_block3').style.display='none';
localStorage.teachingEnd=1;
},
failBorder: function()
{
return;
if(localStorage.failBorderNotShow) return;
document.getElementById('teaching_fail_border').style.display='block';
},
failOut: function()
{
return;
if(localStorage.failOutNotShow) return;
document.getElementById('teaching_fail_out').style.display='block';
},
failOutClose: function()
{
localStorage.failOutNotShow=1;
document.getElementById('teaching_fail_out').style.display='none';
},
failBorderClose: function()
{
localStorage.failBorderNotShow=1;
document.getElementById('teaching_fail_border').style.display='none';
},
playerSetPos: function(x, y)
{
nodeBlock2.style.left=Math.round(x*fieldScale+12)+'px';
nodeBlock2.style.bottom=Math.round((40+y)*fieldScale)+'px';
nodeBlock2.style.width='auto';
nodeBlock2_2.style.left=Math.round(x*fieldScale+12)+'px';
nodeBlock2_2.style.top=Math.round((53-y)*fieldScale)+'px';
},
collision: function(type, number)
{
//console.log(Main.game.rally.ball.va);
console.log(type, number);
if(teaching.stage==4)
{
if(type=='field' && number==3)
{
toastSuccess();
teaching.stage4success=true;
}
}
else if(teaching.stage==5)
{
if(type=='field' && number==2)
{
teaching.stage5success=true;
toastSuccess();
}
}
else if(teaching.stage==6 && type=='player')
{
if(Math.abs(Main.game.rally.ball.va)>=0.015)
{
toastSuccess();
teaching.stage6success=true
}
}
else if(teaching.stage==7)
{
if(type=='player')
{
teaching.stage7success1=false;
if(Main.game.rally.ball.va>=0.02)
{
toastSuccess();
teaching.stage7success2=true
}
else teaching.stage7success2=false;
}
else if(type=='field' && number==3)
{
teaching.stage7success1=true;
if(teaching.stage7success2) toastSuccess();
}
}
else if(teaching.stage==11)
{
if(type=='player')
{
teaching.stage11success=false;
}
else if(type=='field' && (number==2 || number==3))
{
teaching.stage11success=true;
toastSuccess();
teaching.stage11successCount++;
}
}
},
rallyStart: function()
{
if(teaching.stage>=4 && teaching.stage<=7)
{
playerShadowAnimateStop();
}
},
rallyEnd(whoWin)
{
if(teaching.stage==3) teaching.step4();
else if(teaching.stage==4)
{
if(teaching.stage4success) teaching.step5();
else
{
animate4();
toast('Мимо! Попробуйте еще раз.');
}
}
else if(teaching.stage==5)
{
if(teaching.stage5success) teaching.step6();
else
{
animate5();
toast('Мимо! Попробуйте еще раз.');
}
}
else if(teaching.stage==6)
{
if(teaching.stage6success) teaching.step7();
else
{
toast('Слабовато! Закрутите посильнее.');
animate6();
}
}
else if(teaching.stage==7)
{
if(! teaching.stage7success1)
{
animate7();
toast('Мимо! Попробуйте еще раз.');
}
else if(! teaching.stage7success2)
{
animate7();
toast('Слабое вращение! Попробуйте еще раз.');
}
else
{
teaching.step11();
}
}
else if(teaching.stage==11)
{
if(! teaching.stage11success)
{
toast('Мимо! Попробуйте еще раз.');
}
else
{
if(teaching.stage11successCount>=2)
{
toastSuccess();
teaching.step12();
}
else
{
toast('Здорово! И еще разок для закрепления.');
}
}
}
},
skip: function()
{
localStorage.teachingEnd=1;
window.location.href='/?teaching_end';
}
};
})();
toastNode=document.getElementById('toast');
toastContNode=document.getElementById('toast_cont');
toastTimer=false;
toast=function(mes, t=3500)
{
if(toastTimer) clearTimeout(toastTimer);
toastContNode.innerHTML=mes;
toastNode.style.display='block';
toastNode.style.width='auto';
toastTimer=setTimeout(function(){toastNode.style.display='none';}, t);
}
toastSuccessMess=['Супер!', 'Отлично!', 'Получилось!'];
toastSuccess=function()
{
toast(toastSuccessMess[parseInt((Math.random()-0.0000001)*toastSuccessMess.length)], 1500)
}
Main.init();
var lobbyClose=document.querySelector('#lobby_close');
//lobbyClose.addEventListener('click', function(){document.body.classList.add('lobby_closed');});
var lobbyOpen=document.querySelector('#lobby_open');
//lobbyOpen.addEventListener('click', function(){document.body.classList.remove('lobby_closed');});
document.getElementById('help_close').addEventListener('click', function(){localStorage.helpClosed=1;document.body.classList.add('help_closed');});
document.getElementById('help_open').addEventListener('click', function(){localStorage.helpClosed='';document.body.classList.remove('help_closed');});
document.querySelector('#chat #chat_close').addEventListener('click', function(){document.body.classList.add('chat_closed');});
document.getElementById('chat_open').addEventListener('click', function(){document.body.classList.remove('chat_closed');});
teaching.start();
});
var networkClient=false;
var time=false;
var gameStopWait=false;
var gameStopped=false;
Main=class
{
static init()
{
networkClient=Main.networkClient=NetworkClient.NetworkClient('tennis.thelv.ru', 8083);
time=Game.Rally.Time.Time();
Main.view=MainView();
if(! localStorage.auth) localStorage.auth=(Math.random())+(Math.random())+(Math.random())+(Math.random());
//networkClient=Main.networkClient=NetworkClient.NetworkClient('tennis2d.org', 8084);
Main.gameCreate('local', false, time.get());
if(localStorage.name) Main.nameSet(localStorage.name);
}
static messageSend(message, sendType=null)
{
Main.networkClient.messageSend(message, sendType);
}
static gameCreate(type, whoMain, t, opponent=false, state=false)
{
if(type=='network') lobby.hide(); else if(type=='local') lobby.show();
gameStopped=false;
document.body.classList.remove('game_local');
document.body.classList.remove('game_network');
document.body.classList.remove('game_view');
document.body.classList.add('game_'+type);
if(Main.game)
{
Main.game.destroy();
}
Main.game = Game.Game(type, whoMain, t, opponent, state);
}
static connectionEstablished(reconnect)
{
Main.messageSend({auth: localStorage.auth, name: localStorage.name, reconnect: reconnectIs});
}
static messageReceive(message)
{
switch(message.tp)
{
case 'user_id':
Main.userId=message.user_id;
break;
case 'users':
Main.view.users(message);
break;
case 'invites':
Main.view.invites(message);
break;
case 'game_create':
Main.gameCreate('network', message.first_serve, message.t, message.opponent);
break;
case 'name_set':
Main.nameSet(message.name);
break;
case 'game_stop':
Main.gameStop();
break;
case 'game_view':
Main.gameCreate('view', message.first_serve, message.t, false, message);
break;
case 'chat':
chat.receive(message);
break;
case 'game_view_leave':
Main.gameViewLeave();
break;
case 'new_window_opened':
Main.newWindowOpened();
break;
}
}
static nameSet(name)
{
localStorage.name=name;
Main.view.name(name);
}
static nameChange()
{
var name='';
if(name=prompt('Введите имя:'))
{
Main.nameSet(name);
Main.messageSend({tp: 'name_set', name: name});
}
}
static invite(id)
{
if(Main.game.type=='network' && ! Main.game.stopped)
{
alert('Чтобы начать новую игру, необходимо выйти из этой.');
return;
}
networkClient.messageSend({tp: 'invite_send', id: id});
}
static uninvite(id)
{
networkClient.messageSend({tp: 'invite_cancel', id: id});
}
static gameLeave()
{
if(gameStopped)
{
Main.gameCreate('local', false, time.getAbs());
gameStopped=false;
}
else if(Main.game.type!=='view')
{
networkClient.messageSend({tp: 'game_leave'});
gameStopWait=true;
}
else
{
networkClient.messageSend({tp: 'game_view_leave'});
}
}
static gameView(id)
{
networkClient.messageSend({tp: 'game_view', id: id});
}
static gameStop()
{
gameStopped=true;
if(gameStopWait)
{
Main.gameCreate('local', false, time.getAbs());
gameStopWait=false;
}
else
{
//Main.game.rally.player0.view.hide();
//Main.view.gameLeaveOpponent();
Main.game.opponentLeave();
lobby.show();
}
}
static gameViewLeave()
{
Main.gameCreate('local', false, time.getAbs());
lobby.show();
}
static newWindowOpened()
{
networkClient.stop();
Main.view.newWindowOpened();
}
}
Main.stage=
{
addChild: function(a)
{
//
}
}
MainView=function()
{
var nameNode=document.querySelector('#name');
var usersFreeNode=document.querySelector('#users_free_list');
var usersNotFreeNode=document.querySelector('#users_not_free_list');
var usersInvitesWhoNode=document.querySelector('#users_invites_who_list');
var usersInvitesFromNode=document.querySelector('#users_invites_from_list');
var gameLeaveNode=document.querySelector('#game_leave');
var waitNode=document.querySelector('#wait');
document.querySelector('#game_leave').addEventListener('click', function()
{
Main.gameLeave();
});
document.querySelector('#name_change').addEventListener('click', function()
{
Main.nameChange();
});
var userNodeCreate=function(user, title)
{
var e=document.createElement('div');
e.setAttribute('class', 'user');
if(title) e.setAttribute('title', title);
e.innerHTML=''+user.name+'';
return e;
}
var res=
{
name: function(name)
{
nameNode.innerText=name;
},
users: function(users)
{
var free=users.free;
usersFreeNode.innerHTML='';
for(var i in free)
{
var user=free[i];
var e=userNodeCreate(user, 'Пригласить в игру');
(function(id)
{
e.addEventListener('click', function()
{
Main.invite(id);
});
})(user.id);
if(user.id!=Main.userId) usersFreeNode.appendChild(e);
}
if(free.length==0 || free.length==1)
{
usersFreeNode.innerText='Никого нет.';
}
/*var notFree=users.not_free;
usersNotFreeNode.innerHTML='';
for(var i in notFree)
{
var user=notFree[i];
var e=userNodeCreate(user, 'Пригласить в игру');
(function(id)
{
e.addEventListener('click', function()
{
Main.invite(id);
});
})(user.id);
usersNotFreeNode.appendChild(e);
}
if(notFree.length==0)
{
usersNotFreeNode.innerText='Никого нет.';
}*/
var games=users.games;
usersNotFreeNode.innerHTML='';
for(var i in games)
{
var game=games[i];
var user0=game.players[1];
var user1=game.players[0];
var gameNode=document.createElement('div');
gameNode.setAttribute('class', '_game');
var e1=userNodeCreate(user0);
(function(id)
{
e1.addEventListener('click', function()
{
///Main.invite(id);
});
})(user0.id);
var e2=userNodeCreate(user1);
(function(id)
{
e2.addEventListener('click', function()
{
//Main.invite(id);
});
})(user1.id);
gameNode.appendChild(e1);
var vs=document.createElement('span');
vs.innerHTML=' с ';
gameNode.appendChild(vs);
gameNode.appendChild(e2);
usersNotFreeNode.appendChild(gameNode);
(function(id)
{
gameNode.addEventListener('click', function()
{
Main.gameView(id);
});
})(game.id);
}
if(games.length==0)
{
usersNotFreeNode.innerText='Нет игр.';
}
},
invites: function(invites)
{
var who=invites.invites_who;
usersInvitesWhoNode.innerHTML='';
for(var i in who)
{
var user=who[i];
var e=userNodeCreate(user, 'Отменить приглашение');
(function(id)
{
e.addEventListener('click', function()
{
Main.uninvite(id);
});
})(user.id);
usersInvitesWhoNode.appendChild(e);
}
if(who.length==0)
{
usersInvitesWhoNode.innerText='Пригласите игрока онлайн поиграть.';
}
var from=invites.invites_from;
usersInvitesFromNode.innerHTML='';
for(var i in from)
{
var user=from[i];
var e=userNodeCreate(user, 'Начать игру');
(function(id)
{
e.addEventListener('click', function()
{
Main.invite(id);
});
})(user.id);
usersInvitesFromNode.appendChild(e);
}
if(from.length==0)
{
usersInvitesFromNode.innerText='Вас пока никто не пригласил.';
}
},
gameLeaveOpponent: function()
{
waitNode.innerHTML='Оппонент покинул игру';
waitNode.style.display='block';
},
newWindowOpened: function()
{
document.getElementById('new_window_opened').style.display='flex';
}
}
return res;
}
class MathLib
{
static vAngle(v)
{
var x=v[0];
var y=v[1];
return x==0 ? (y>0 ? Math.PI/2 : -Math.PI/2) : (x>0 ? Math.atan(y/x) : Math.atan(y/x)+Math.PI);
}
static getAngleByCoords(x, y)
{
return x==0 ? (y>0 ? Math.PI/2 : -Math.PI/2) : (x>0 ? Math.atan(y/x) : Math.atan(y/x)+Math.PI);
}
static getLengthByCoords(x, y)
{
return Math.sqrt(x*x + y*y);
}
static linesIntersection(x00, y00, x01, y01, x10, y10, x11, y11)
{
if((x00==x01 && y00==y01)||(x10==x11 && y10==y11))
{
return {intersect: false, linesIntersect: false};
}
if(x00==x01 && x10==x11)
{
return {intersect: false, linesIntersect: false};
}
if(x00==x01 || (x10==x11 && y00!=y01))
{
var reverse=true;
var m=x00;x00=y00;y00=m;
m=x01;x01=y01;y01=m;
m=x10;x10=y10;y10=m;
m=x11;x11=y11;y11=m;
}
else
{
var reverse=false;
}
if(x10==x11 && y00==y01)
{
x=x10;
y=y00;
var p0=(x-x00)/(x01-x00);
var p1=(y-y10)/(y11-y10);
}
else
{
var k0=(y01-y00)/(x01-x00);
var k1=(y11-y10)/(x11-x10);
if(k0==k1)
{
return {intersect: false, linesIntersect: false};
}
var x=(k0*x00-k1*x10-y00+y10)/(k0-k1);
var y=y00+k0*(x-x00);
var p0=(x-x00)/(x01-x00);
var p1=(x-x10)/(x11-x10);
}
if (p1>=0 && p1<=1)
{
if(p0>0 && p0<=1)
{
if(reverse)
{
m=x;x=y;y=m;
}
return {intersect: true, linesIntersect: true, x: x, y:y, k: p0}
}
else
{
return {intersect: false, linesIntersect: true, k: p0};
}
}
else
{
return {intersect: false, linesIntersect: false};
}
}
static intersectionWithMovingLine(x00, y00, x01, y01, x10, y10, x11, y11, x20, y20, x21, y21)
{
var intersection1=linesIntersection(x00, y00, x01, y01, x10, y10, x11, y11);
var intersection2=linesIntersection(x00, y00, x01, y01, x20, y20, x21, y21);
if(intersection1.intersect)
{
return intersection1;
}
if(intersection2.intersect)
{
return intersection2;
}
if(intersection1.linesIntersect && intersection2.linesIntersect)
{
var k1=intersection1.k;
var k2=intersection2.k;
if(k1>k2)
{
var m=k1;k1=k2;k2=m;
}
if((k2>0 && k2<=1) || (k1<=1 && k2>1))
{
var k=(Math.max(k1, 0)+Math.min(k2, 1))/2;
var x=x00+(x01-x00)*k;
var y=y00+(y01-y00)*k;
return {intersect: true, k: k, x: x, y: y};
}
else
{
return {intersect: false};
}
}
else
{
return {intersect: false};
}
}
static getLineDirection(x0, y0, x1, y1)
{
var x = x1 - x0;
var y = y1 - y0;
var l = MathLib.getLengthByCoords(x, y);
if (l != 0)
{
x = x / l;
y = y / l;
}
return [x, y];
}
static decomposeVector(vx, vy, x, y)
{
var vt = vx * x + vy * y;
var vn = - vx * y + vy * x;
return [vt, vn];
}
static composeVector(vt, vn, x, y)
{
var vx = vt * x - vn * y;
var vy = vt * y + vn * x;
return [vx, vy];
}
static bounceOfRotatingBall(vt, vn, w, r, k, y)
{
w = -w;
var sgn = (vt < w*r) ? 1 : -1;
var dv=((w*r-vt)*sgn / (k*(y+1)) > 2*vn) ? 2*vn*k*sgn : (w*r-vt)/(y+1);
var dw = - (-dv*y/r);
return [dv, dw];
}
/* "v" library - vector operations */
static vPlus(a, b)
{
return [a[0] + b[0], a[1] + b[1]];
}
static vMinus(a, b)
{
return [a[0] - b[0], a[1] - b[1]];
}
static vTurn(v, dir)
{
return [v[0] * dir[0] - v[1] * dir[1], v[1] * dir[0] + v[0] * dir[1]];
}
static vReturn(v, dir)
{
return [v[0] * dir[0] + v[1] * dir[1], v[1] * dir[0] - v[0] * dir[1]];
}
static vLength(v)
{
return Math.sqrt(v[0] * v[0] + v[1] * v[1]);
}
static vLengthSqr(v)
{
return v[0] * v[0] + v[1] * v[1];
}
static vNormalize(v)
{
if (v[0] == 0 && v[1] == 0)
{
return [0, 0];
}
else
{
var l = MathLib.vLength(v);
return [v[0] / l, v[1] / l];
}
}
static vProduct(v, a)
{
return [a * v[0], a * v[1]];
}
static vVectorProduct(a, b)
{
return -a[0] * b[1] + a[1] * b[0];
}
}
View=function()
{
var e=document.createElement('div');
//e.innerHTML='view';
e.setAttribute('class', 'view');
document.querySelector('#center').append(e);
return {
showPosition: function(x, y, a)
{
e.style.left=x+'px';
e.style.top=-y+'px';
},
addChild: function()
{
//
}
}
}
NetworkClient.NetworkClient=function(host, port)
{
var ws=false;
reconnectIs=false;
var stopped=false;
var res=
{
NetworkClient: function(host, port)
{
host=host;
port=port;
this.connect();
},
messageSend: function(message)
{
if(ws) ws.send(JSON.stringify(message), function(){});
},
reconnect: function()
{
if(stopped) return;
reconnectIs=true;
if(ws)
{
ws=false;
setTimeout(res.connect, 1000);
}
},
connect: function()
{
ws=new WebSocket('wss://'+host+':'+port);
ws.onclose=function()
{
res.reconnect();
};
ws.onerror=function()
{
res.reconnect();
};
ws.onmessage=function(message)
{
message=JSON.parse(message.data);
if (message.g)
{
Main.game.messageReceive(message);
}
else
{
Main.messageReceive(message);
}
};
ws.onopen=function()
{
Main.connectionEstablished(reconnectIs);
};
},
stop: function()
{
stopped=true;
}
}
res.NetworkClient(host, port);
return res;
}
Game.Game=function(type, whoMain, t, opponent=false, state=false)
{
var
view=0,
rally=0,
wait=0,
referee=0;
//view
var gameHeadNode=document.querySelector(false && (type=='local') ? '#game_local_head' : '#game_network_head');
var gameCaptionNode=gameHeadNode.querySelector('._caption');
var res=
{
get type(){return type;}, set type(a){type=a;},
get whoMain(){return whoMain;}, set whoMain(a){whoMain=a;},
get view(){return view;}, set view(a){view=a;},
get rally(){return rally;}, set rally(a){rally=a;},
get wait(){return wait;}, set wait(a){wait=a;},
get referee(){return referee;}, set referee(a){referee=a;},
stopped: false,
Game: function(type, whoMain, t, state)
{
//view
this.type = type;
this.whoMain = whoMain;
view = Game.GameView();
Main.stage.addChild(view);
rally = Game.Rally.Rally(this);
switch(type)
{
case 'local':
wait = Game.Wait.Wait(this);
break;
case 'network':
wait=Game.Wait.NetworkWait(this);
break;
case 'view':
wait=Game.Wait.ViewWait(this);
break;
}
referee=Game.Referee.Referee(this, (this.type=='view') ? ! state.state.who_serve: whoMain);
time.reset(t);
if(type!='local')
{
time.sync();
}
referee.start();
//referee.start() executes from string time.synchronize()
rally.viewShowServeLines(true);
gameHeadNode.style.display='block';
if(type=='network')
{
gameCaptionNode.innerText='Матч с '+opponent.name;
}
else if(type=='local')
{
gameCaptionNode.innerText='Матч против компьютера';
}
else if(type=='view')
{
waitView.ready('Ожидание готовности игроков');
this.setState(state.state);
gameCaptionNode.innerText='Матч '+state.players[1].name+' с '+state.players[0].name;
}
},
messageReceive: function(message)
{
switch(message.tp)
{
case 'pcp':
case 'pcpa':
rally.player1.messageReceive(message);
break;
case 'bh':
rally.ball.messageReceive(message);
break;
case 'rw':
rally.referee.messageReceive(message);
break;
case 'wr':
wait.messageReceive(message);
break;
/*case 'ts':
rally.time.messageReceive(message);
break;*/
}
},
messageSend: function(message, sendType=null)
{
message.g=true;
if (type=='network')
{
Main.networkClient.messageSend(message, sendType);
}
},
destroy: function()
{
rally.ball.destroy();
rally.timer.unbind();
rally.player0.unbind();
rally.player1.unbind();
wait.unbind();
gameHeadNode.style.display='none';
},
opponentLeave: function()
{
wait.opponentLeave();
rally.ball.opponentLeave();
rally.player1.hide();
if(this.type=='view') rally.player0.hide();
this.stopped=true;
}
}
if(type=='view')
{
res.setState=function(state)
{
console.log(state);
res.referee.scoreSet(state.score);
if(state.wait>0)
{
wait.start(state.wait);
}
else if(state.wait==-1)
{
wait.wait();
}
else
{
wait.start(0);
}
if(state.ball.h) res.messageReceive_(state.ball.h);
if(state.players[0].cp) res.messageReceive_(state.players[0].cp);
if(state.players[0].cpa) res.messageReceive_(state.players[0].cpa);
if(state.players[1].cp) res.messageReceive_(state.players[1].cp);
if(state.players[1].cpa) res.messageReceive_(state.players[1].cpa);
}
res.messageReceive_= function(message)
{
switch(message.tp)
{
case 'pcp':
if(message.side)
{
message.x=-message.x;
message.y=-message.y;
message.vx=-message.vx;
message.vy=-message.vy;
message.mx=-message.mx;
message.my=-message.my;
rally.player0.messageReceive(message);
}
else
{
rally.player1.messageReceive(message);
}
break;
case 'pcpa':
if(message.side)
{
//message.a=message.a-Math.PI;
rally.player0.messageReceive(message);
}
else
{
rally.player1.messageReceive(message);
}
break;
case 'bh':
if(message.side)
{
message.x=-message.x;
message.y=-message.y;
message.vx=-message.vx;
message.vy=-message.vy;
}
rally.ball.messageReceive(message);
break;
case 'rw':
if(message.side)
{
message.w=! message.w;
}
rally.referee.messageReceive(message);
break;
case 'wr':
if(message.t>0)
{
wait.messageReceive(message);
break;
}
/*case 'ts':
rally.time.messageReceive(message);
break;*/
}
};
res.messageReceive=function(message)
{
res.messageReceive_(message);
}
}
res.Game(type, whoMain, t, state);
return res;
}
Game.GameView=View;
/*{
GameView()
{
//super(1, 1);
//x = 600;
//y = 330;
}
}*/
Game.Rally.Rally=function(game)
{
var Referee=Game.Rally.Referee.Referee;
var FieldLines=Game.Rally.FieldLines.FieldLines;
var BorderLines=Game.Rally.BorderLines.BorderLines;
var MiddleLines=Game.Rally.MiddleLines.MiddleLines;
var ServeLines=Game.Rally.ServeLines.ServeLines;
var Time=Game.Rally.Time.Time;
var Timer=Game.Rally.Timer.Timer;
var Player=Game.Rally.Player.Player;
var LocalPlayer=Game.Rally.Player.LocalPlayer;
var BotPlayer=Game.Rally.Player.BotPlayer;
var RemotePlayer=Game.Rally.Player.RemotePlayer;
var Ball=Game.Rally.Ball.Ball;
var referee=0, fieldLines=0, borderLines=0, middleLines=0, serveLines=0, time=0, timer=0, ball=0, player0=0, player1=0;
var serveLinesNode=document.querySelector('#border_serve');
var res=
{
get game(){return game;}, set game(a){game=a;},
get referee(){return referee;}, set referee(a){referee=a;},
get fieldLines(){return fieldLines;}, set fieldLines(a){fieldLines=a;},
get borderLines(){return borderLines;}, set borderLines(a){borderLines=a;},
get middleLines(){return middleLines;}, set middleLines(a){middleLines=a;},
get serveLines(){return serveLines;}, set serveLines(a){serveLines=a;},
get time(){return time;}, set time(a){time=a;},
get timer(){return timer;}, set timer(a){timer=a;},
get ball(){return ball;}, set ball(a){ball=a;},
get player0(){return player0;}, set player0(a){player0=a;},
get player1(){return player1;}, set player1(a){player1=a;},
Rally: function(game)
{
this.game = game;
time = window.time;
fieldLines = FieldLines();
borderLines = BorderLines();
middleLines = MiddleLines();
serveLines = ServeLines();
referee = Referee(game);
ball = Ball(game);
player0 = (game.type!=='view') ? LocalPlayer(game, true) : RemotePlayer(game, true);
player1 = (game.type == 'local') ? BotPlayer(game, false) : RemotePlayer(game, false);
//view
/*game.view.addChild(middleLines.view);
game.view.addChild(serveLines.view);
game.view.addChild(fieldLines.view);
game.view.addChild(borderLines.view);*/
game.view.addChild(player0.view);
game.view.addChild(player1.view);
game.view.addChild(ball.view);
//start
timer = Timer(game);
},
shiftTime: function(t)
{
middleLines.lines[0].shiftTime(t);
middleLines.lines[1].shiftTime(t);
player0.shiftTime(t);
player1.shiftTime(t);
ball.shiftTime(t);
},
viewShowServeLines: function(show)
{
if (show)
{
serveLines.view.visible = true;
serveLinesNode.style.display='block';
}
else
{
serveLines.view.visible = false;
serveLinesNode.style.display='none';
}
}
}
res.Rally(game);
return res;
}
Game.Rally.Ball.Ball=function(game)
{
var BallEval=Game.Rally.Ball.BallEval.BallEval;
var BallCollisions=Game.Rally.Ball.BallCollisions.BallCollisions;
var eval=0, collisions=0, x=0, y=0, vx=0, vy=0, va=0, a=0, r=4, view=0;
var res=
{
get game(){return game;}, set game(a){game=a;},
get eval(){return eval;}, set eval(a){eval=a;},
get collisions(){return collisions;}, set collisions(a){collisions=a;},
get x(){return x;}, set x(a){x=a;},
get y(){return y;}, set y(a){y=a;},
get vx(){return vx;}, set vx(a){vx=a;},
get vy(){return vy;}, set vy(a){vy=a;},
get va(){return va;}, set va(a){va=a;},
get a(){return a;}, set a(b){a=b;},
get r(){return r;}, set r(a){r=a;},
get view(){return view;}, set view(a){view=a;},
//constructor
Ball: function(game)
{
this.game = game;
this.eval = BallEval(this);
this.a = 0;
this.setControlPoint(0, 0, 0, 0, 0, 0);
this.collisions = BallCollisions(game, this);
//view
view = Game.Rally.Ball.BallView(this);
this.viewShowPos();
},
//view methods
viewShowPos: function()
{
view.showPosition(this.x, this.y, this.a);
},
//logic methods
setControlPoint(x, y, vx, vy, va, t)
{
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.va = va;
eval.init(t);
if(game.type=='local' && game.rally.player1)
{
game.rally.player1.ballHit(x, y, vx, vy, va, t);
}
},
shiftTime: function(t)
{
eval.eval(t);
collisions.collise(t);
this.viewShowPos(); //view
//game.rally.middleLines.ballPos(x);
},
serve: function(start, who, t)
{
if (start)
{
this.setControlPoint(0, 0, 0.25 * (who ? 1: -1), 0, 0, t);
collisions.init(t);
}
else
{
this.setControlPoint(0, 0, 0, 0, 0, game.rally.time.get());
collisions.init(t);
}
},
messageSendHit: function(t)
{
game.messageSend(
{tp: 'bh', t: t, x: this.x, y: this.y, vx: this.vx, vy: this.vy, va: this.va}
,{firstConnection: 9, connectionsRange: 3, connectionsCount: 2, seriesName: 'bh'}
);
//"bh" == "ball hit"
},
messageReceive: function(message)
{
switch(message.tp)
{
case 'bh':
this.setControlPoint( -message.x, -message.y, -message.vx, -message.vy, message.va, message.t);
game.rally.middleLines.hit(1, -message.x, message.t);
game.rally.referee.collision('player', 1);
collisions.hitWas(1);
this.shiftTime(game.rally.time.get());
break;
}
},
destroy: function()
{
view.remove();
},
opponentLeave: function()
{
this.setControlPoint(x, y, 0, 0, va, game.rally.time.get());
}
}
res.Ball(game);
return res;
}
Game.Rally.Ball.BallView=function()
{
var e=document.createElement('div');
e.innerHTML='