DEMO: http://cnwander.com/demo/billiards/
Original address: http://cnwander.com/blog/?p=11
JS mini game series:
[JS mini-game] Snake + detailed comments
Paste the code first:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd ">
<html xmlns=" http://www.w3.org/1999/xhtml ">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Billiards by CNwander</title>
<style type="text/css">
* {margin:0; padding:0}
body {background:black; text-align:center; font-size:12px}
h1 {font-size:12px; color:gray; font-weight:normal; line-height:200%}
h1 .sub {vertical-align:super; color:red; font-size:9px; }
.info {position:absolute; right:0; color:gray}
#table {position:relative; width:800px; margin:20px auto 10px; height:544px; background:url( ); _background:none; _filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled='true', sizingMethod=' scale', src=" ); _background:none; _filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled='true', sizingMethod='scale', src=" ); _background:none; _filter: progid:DXImageTransform.Microsoft .AlphaImageLoader(enabled='true', sizingMethod='scale', src=" ) no-repeat}
#force {position:absolute; left:0; top:18px; width:75px; height:20px; background:url( ) no-repeat; font-size:1px}
#scoreBoard {position:absolute; z-index:3; top:230px; left:346px; font-size:50px; color:white; filter:alpha(opacity=0); -moz-opacity:0; opacity:0 }
#tips {padding:15px 0 0 20px; text-align:left; color:red; font-size:12px}
</style>
<script type="text/javascript">
// common
function $(str) {
return document.getElementById(str);
}
function $tag(str,target) {
target = target || document;
return target.getElementsByTagName(str);
}
function addEventHandler(obj,eType,fuc){
if(obj.addEventListener){
obj.addEventListener(eType,fuc,false);
}else if(obj.attachEvent){
obj.attachEvent("on" + eType,fuc);
}else{
obj["on" + eType] = fuc;
}
}
function removeEventHandler(obj,eType,fuc){
if(obj.removeEventListener){
obj.removeEventListener(eType,fuc,false);
}else if(obj.attachEvent){
obj.detachEvent("on" + eType,fuc);
}
}
function randowNum(start,end) {
return Math.floor(Math.random()*(end - start)) + start;
}
Array.prototype.remove=function(dx) {
if(isNaN(dx)||dx>this.length){return false;}
for(var i=0,n=0;i<this.length;i++)
{
if(this[i]!=this[dx])
{
this[n++]=this[i]
}
}
this.length-=1
}
//const
var TOTALR = 15, //The radius of the ball (including shadow)
R = 12, //The true radius of the ball
POKER = 20,
W = 736, //case width
H = 480, //case height
THICKNESS = 32, //edge thickness
RATE = 100, //refresh frequency
F = 0.01, //friction force
LOSS = 0.2, // Collision speed loss
TIPS = ["Tip1: Reference ball, target ball, target bag, three points and one line, this is the most basic way to score goals","Tip2: The blue bar in the lower right corner represents the hitting force. Small force is easier to control the cue ball Position","Tip3: The blue dot on the white ball in the lower right corner controls the hitting point. It controls the high bar, low bar and plug. The difference between masters and masters is often here","Tip4: In table tennis, the target ball is not actually played. , is the cue ball"];
var table, //case
cueBall, //cue ball
guideBall, //reference ball
dotWrap, //reference line
speed = 12,
rollUp = 0,
rollRight = 0,
timer,
forceTimer,
balls = [],
movingBalls = [],
pokes = [[0,0],[W/2,-5],[W,0],[0,H],[W/2,H+5],[W,H]],
hasShot = false;
shots = 0; //Number of combo shots
window.onload = function() {
initTable();
initShootPos();
showTips();
startGame();
}
function startGame() {
initBall();
addEventHandler(table,"mousemove",dragCueBall);
addEventHandler(table,"mouseup",setCueBall);
}
function initTable() {
table = $("table");
var dotWrapDiv = document.createElement("div"),
guideBallDiv = document.createElement("div");
dotWrapDiv.id = "dotWrap";
guideBallDiv.className = "guide ball";
setStyle(guideBallDiv,"display","none");
dotWrap = table.appendChild(dotWrapDiv);
guideBall = table.appendChild(guideBallDiv);
}
function initBall() {
//Add cue ball
cueBall = new Ball("cue",170,H/2);
balls.push(cueBall);
//Add target ball
for(var i = 0; i < 5; i++) {
for(var j = 0; j <= i; j++) {
var ball = new Ball("target",520 + i*2*R, H/2 - R*i + j*2*R);
balls.push(ball);
}
}
}
function initShootPos() {
var wrap = $("shootPos"),
handler = $("dot"),
arrowR = 18;
addEventHandler(wrap,"mousedown",selectDot);
function selectDot(e) {
e = e || event;
var pos = getElemPos(wrap),
x = e.clientX - pos[0] - handler.offsetWidth/2,
y = e.clientY - pos[1] - handler.offsetHeight/2;
if(Math.sqrt((x-22)*(x-22) + (y-22)*(y-22)) > arrowR) {
var angle = Math.atan2(x-22,y-22);
x = arrowR*Math.sin(angle) + 22;
y = arrowR*Math.cos(angle) + 22;
}
setPos(handler,x,y);
}
}
function getElemPos(target,reference) {
reference = reference || document;
var left = 0,top = 0;
return getPos(target);
function getPos(target) {
if(target != reference) {
left += target.offsetLeft;
top += target.offsetTop;
return getPos(target.parentNode);
} else {
return [left,top];
}
}
}
// ball class
function Ball(type,x,y) {
var div = document.createElement("div");
div.className = type + "ball";
this.elem = table.appendChild(div);
this.type = type;
this.x = x; //position
this.y = y;
this.angle = 0; //Angle
this.v = 0; //Speed (excluding direction)
setBallPos(this.elem,x,y);
return this;
}
function setCueBall() {
removeEventHandler(table,"mousemove",dragCueBall);
removeEventHandler(table,"mouseup",setCueBall);
startShot();
}
function startShot() {
show(cueBall.elem);
addEventHandler(table,"mousemove",showGuide);
addEventHandler(table,"mousedown",updateForce);
addEventHandler(table,"mouseup",shotCueBall);
}
function dragCueBall(e) {
var toX,toY;
e = e || event;
toX = e.clientX - table.offsetLeft - THICKNESS,
toY = e.clientY - table.offsetTop - THICKNESS;
toX = toX >= R ? toX : R;
toX = toX <= 170 ? toX : 170;
toY = toY >= R ? toY : R;
toY = toY <= H - R ? toY : H - R;
setBallPos(cueBall,toX,toY);
}
function shotCueBall() {
removeEventHandler(table,"mousemove",showGuide);
removeEventHandler(table,"mousedown",updateForce);
removeEventHandler(table,"mouseup",shotCueBall);
window.clearInterval(forceTimer);
speed = $("force").offsetWidth * 0.15;
var dotDisX = $("dot").offsetLeft-22,
dotDisY = $("dot").offsetTop-22,
dotDis = Math.sqrt(dotDisX*dotDisX + dotDisY*dotDisY),
dotAngle = Math.atan2(dotDisX,dotDisY);
rollRight = Math.round(dotDis*Math.sin(dotAngle))/5;
rollUp = -Math.round(dotDis*Math.cos(dotAngle))/5;
var formPos = getBallPos(cueBall.elem),
toPos = getBallPos(guideBall),
angle = Math.atan2(toPos[0] - formPos[0],toPos[1] - formPos[1]);
hide(dotWrap);
hide(guideBall);
cueBall.v = speed;
cueBall.angle = angle;
movingBalls.push(cueBall);
timer = window.setInterval(roll,1000 / RATE);
}
function showGuide(e) {
var fromX,fromY,toX,toY;
e = e || event;
toX = e.clientX - table.offsetLeft - THICKNESS,
toY = e.clientY - table.offsetTop - THICKNESS;
setBallPos(guideBall,toX,toY);
show(dotWrap);
show(guideBall);
drawLine();
//reference line
function drawLine() {
var dotNum = 16,
pos = getBallPos(cueBall.elem);
dotWrap.innerHTML = "";
fromX = pos[0];
fromY = pos[1];
var partX = (toX - fromX) / dotNum,
partY = (toY - fromY) / dotNum;
for(var i = 1; i < dotNum; i++) {
var x = fromX + partX * i,
y = fromY + partY * i;
drawDot(dotWrap, x, y);
}
}
}
function roll() {
if(movingBalls.length <= 0) {
if(!hasShot) shots = 0;
else shots ++; //accumulated combos
hasShot = false;
setStyle($("force"),"width",80+"px");
setPos($("dot"),22,22);
window.clearInterval(timer);
if(shots > 1) showScore(shots); //Show the number of combos
startShot();
}
for(var i = 0; i < movingBalls.length; i++) {
var ball = movingBalls[i],
sin = Math.sin(ball.angle),
cos = Math.cos(ball.angle);
ball.v -= F;
//Remove the stationary ball
if(Math.round(ball.v) == 0) {
ball.v = 0;
movingBalls.remove(i);
continue;
}
var vx = ball.v * sin,
vy = ball.v * cos;
ball.x += vx;
ball.y += vy;
//Put it in the bag
if(isPocket(ball.x,ball.y)) {
hide(ball.elem);
if(ball.type == "cue") {
if(!hasShot) shots = 0;
hasShot = false;
window.setTimeout(function(){
ball.v = 0;
setBallPos(ball,170,250);
},500);
}else {
//Remove the bagged balls
hasShot = true;
ball.v = 0;
for(var k = 0, l =0; k < balls.length; k++) {
if(balls[k] != ball) {
balls[l++] = balls[k];
}
}
balls.length -= 1;
}
return;
}
//edge collision
if(ball.x < R || ball.x > W - R) {
ball.angle *= -1;
ball.angle %= Math.PI;
ball.v = ball.v * (1 - LOSS);
vx = ball.v*Math.sin(ball.angle);
vy = ball.v*Math.cos(ball.angle);
if(ball.x < R) ball.x = R;
if(ball.x > W - R) ball.x = W - R;
//cue ball stuffing
if(ball.type == "cue") {
if(ball.angle > 0) vy -= rollRight;
else vy += rollRight;
vx += rollUp;
rollUp *= 0.2;
rollRight *= 0.2;
ball.v = Math.sqrt(vx*vx + vy*vy);
ball.angle = Math.atan2(vx,vy);
}
}
if(ball.y < R || ball.y > H - R) {
ball.angle = ball.angle > 0 ? Math.PI - ball.angle : - Math.PI - ball.angle ;
ball.angle %= Math.PI;
ball.v = ball.v * (1 - LOSS);
vx = ball.v*Math.sin(ball.angle);
vy = ball.v*Math.cos(ball.angle);
if(ball.y < R) ball.y = R;
if(ball.y > H - R) ball.y = H - R;
//cue ball stuffing
if(ball.type == "cue") {
if(Math.abs(ball.angle) < Math.PI/2) vx += rollRight;
else vx -= rollRight;
vy += rollUp;
rollUp *= 0.2;
rollRight *= 0.2;
ball.v = Math.sqrt(vx*vx + vy*vy);
ball.angle = Math.atan2(vx,vy);
}
}
//ball collision
for(var j = 0; j < balls.length; j++) {
var obj = balls[j];
if(obj == ball) continue;
var disX = obj.x - ball.x,
disY = obj.y - ball.y,
gap = 2 * R;
if(disX <= gap && disY <= gap) {
var dis = Math.sqrt(Math.pow(disX,2)+Math.pow(disY,2));
if(dis <= gap) {
//If it is stationary, add it to the array movingBalls
if(Math.round(obj.v) == 0)
movingBalls.push(obj);
//Rotate the coordinates to the x-axis for collision calculation
// Calculate angle and sine and cosine values - exact values
//var c = (obj.x*ball.y - obj.y*ball.x)/(2*R),
// d = Math.sqrt(ball.x*ball.x + ball.y*ball.y),
// angle = Math.asin(ball.y/d) - Math.asin(c/d) - ball.angle%(Math.PI/2),
//angle = Math.asin(oy / (2 * R)),
//Restore the tangent state of the two balls - approximate value
ball.x -= (gap - dis)*sin;
ball.y -= (gap - dis)*cos;
disX = obj.x - ball.x;
disY = obj.y - ball.y;
// Calculate angle and sine and cosine values
var angle = Math.atan2(disY, disX),
hitsin = Math.sin(angle),
hitcos = Math.cos(angle),
objVx = obj.v * Math.sin(obj.angle),
objVy = obj.v * Math.cos(obj.angle);
//trace(angle*180/Math.PI);
// Rotate coordinates
var x1 = 0,
y1 = 0,
x2 = disX * hitcos + disY * hitsin,
y2 = disY * hitcos - disX * hitsin,
vx1 = vx * hitcos + vy * hitsin,
vy1 = vy * hitcos - vx * hitsin,
vx2 = objVx * hitcos + objVy * hitsin,
vy2 = objVy * hitcos - objVx * hitsin;
// Velocity and position after collision
var plusVx = vx1 - vx2;
vx1 = vx2;
vx2 = plusVx + vx1;
//cue ball stuffing
if(ball.type == "cue") {
vx1 += rollUp;
rollUp *= 0.2;
}
x1 += vx1;
x2 += vx2;
// Rotate the position back
var x1Final = x1 * hitcos - y1 * hitsin,
y1Final = y1 * hitcos + x1 * hitsin,
x2Final = x2 * hitcos - y2 * hitsin,
y2Final = y2 * hitcos + x2 * hitsin;
obj.x = ball.x + x2Final;
obj.y = ball.y + y2Final;
ball.x = ball.x + x1Final;
ball.y = ball.y + y1Final;
// Rotate the speed back
vx = vx1 * hitcos - vy1 * hitsin;
vy = vy1 * hitcos + vx1 * hitsin;
objVx = vx2 * hitcos - vy2 * hitsin;
objVy = vy2 * hitcos + vx2 * hitsin;
//final speed
ball.v = Math.sqrt(vx*vx + vy*vy) * (1 - 0);
obj.v = Math.sqrt(objVx*objVx + objVy*objVy) * (1 - 0);
// Calculate angle
ball.angle = Math.atan2(vx , vy);
obj.angle = Math.atan2(objVx , objVy);
//break;
}
}
}
setBallPos(ball,ball.x,ball.y);
}
}
function isPocket(x,y) {
if(y < POKER) return check(0,2);
else if (y > H - POKER) return check(3,5);
else return false;
function check(m,n) {
for(var i=m; i<=n; i++) {
if(x >= pokes[i][0] - POKER && x <= pokes[i][0] + POKER) {
var dis = Math.sqrt(Math.pow(x - pokes[i][0],2) + Math.pow(y - pokes[i][1],2));
if(dis <= POKER) return true;
else return false;
}
}
}
}
function getBallPos(obj) {
var pos = [];
pos.push(obj.offsetLeft - THICKNESS + TOTALR);
pos.push(obj.offsetTop - THICKNESS + TOTALR);
return pos;
}
function setPos(obj,x,y) {
obj.style.left = x + "px";
obj.style.top = y + "px";
}
function setBallPos(ball,x,y) {
if(ball.constructor == Ball) {
ball.x = x;
ball.y = y;
ball = ball.elem;
}
setPos(ball,x + THICKNESS - TOTALR,y + THICKNESS - TOTALR);
}
function drawDot(wrap,x,y) {
var elem = document.createElement("div");
setStyle(elem,{
position: "absolute",
width: "1px",
height: "1px",
fontSize: "1px",
background: "white"
});
setPos(elem,x,y);
wrap.appendChild(elem);
}
function updateForce() {
var obj = $("force"),
len = 80,
up = true;
forceTimer = window.setInterval(update,10);
function update() {
if(up) setStyle(obj,"width",len+++"px");
else setStyle(obj,"width",len--+"px");
if(len > 136) up = false;
if(len <= 0) up = true;
}
}
function setStyle() {
if(arguments.length == 2 && typeof arguments[1] == "object") {
for(var key in arguments[1]) {
arguments[0].style[key] = arguments[1][key];
}
} else if (arguments.length > 2) {
arguments[0].style[arguments[1]] = arguments[2];
}
}
function hide(obj) {
setStyle(obj,"display","none");
}
function show(obj) {
setStyle(obj,"display","block");
}
//output information
function trace(sth,who) {
who = who || $("tips");
if(document.all) who.innerText = sth;
else who.textContent = sth;
return who;
}
function showScore(n) {
var wrap = $("scoreBoard");
trace(n+"connecting rod",wrap);
fadeIn(wrap);
}
function fadeIn(obj){
var fromY = 230,
posStep = [8,14,19,23,26,28,29,29,30,30,30],
opaStep = [0,0.05,0.1,0.15,0.2,0.25,0.3,0.4,0.5,0.6,0.8],
fromOpa = 0,
t = 0,
step = posStep.length,
inTimer = window.setInterval(showIn,20),
outTimer;
function showIn() {
setOpacity(obj,opaStep[t]);
obj.style.top = fromY + posStep[t] + "px";
t++;
if(t>=step) {
window.clearInterval(inTimer);
outTimer = window.setInterval(fadeOut,50);
}
}
function fadeOut() {
t--;
setOpacity(obj,opaStep[t]);
obj.style.top = fromY + posStep[t] + "px";
if(t <= 0) {
window.clearInterval(outTimer);
hide(obj);
}
}
}
function setOpacity(obj,n) {
obj.style.cssText = "filter:alpha(opacity="+ n*100 +"); -moz-opacity:"+ n +"; opacity:"+ n;
}
function showTips() {
var i = 0;
tip();
window.setInterval(tip,3000);
function tip() {
trace(TIPS[i++]);
if(i >= TIPS.length) i = 0;
}
}
</script>
</head>
<body>
<div class="info">Discuss: <a href=" http://bbs.blueidea.com/thread-2951566-1-1.html"> Blueidea</a > <a href=" http: //cnwander.com/blog/?p=11">Wander's space</a></div>
<h1>Chinese people stand up! <span class="sub">60th Anniversary</span></h1>
<div id="table">
<div id="scoreBoard"></div>
</div>
<div class="bot">
<div id="tips"></div>
<div class = "ctrl">
<div id="force"></div>
<div id="shootPos">
<div id="dot"></div>
</div>
</div>
</div>
</body>
</html>
Although it is shamelessly named table tennis, it is actually far from real table tennis, and there are still many areas for improvement.
Specific issues to be resolved:
There will definitely be a lot of other problems. I'm worried that I'll get too distracted during the holidays and won't be able to continue later. I'll just finish it all in one go. I'm in a bit of a hurry. I'll solve the problems slowly later. I'll post them first for students who are interested in this topic. Discuss and discuss.
College mathematics is basically just a formality, and I have forgotten very little about physics and mathematics in high school. Only when I actually started doing things did I realize that I was too weak in this area. I hope that students with more experience in this area will give me some advice.