#=========================#
# PHYS113-002             #
# Recitation HW #8        #
# The Basket and The Ball #
# By Alexander Karagodov  #
# 12/06/2007              #
#=========================#

# ...just a comment:
# -control of the ball initial direction
# -control the power of shots
# -animated net under the basket
# all of that and more could be implemented...
# if only I'd start coding before yesterday :-D

# + there is a known bug which allows you to score
#   as much as you want with certain conditions

from visual import *
#from visual.text import *
from random import *

scene.autoscale = 0;

dbg = false; # debug mode switch

# display help
print "Instructions:";
print "use W and S keys to move to/from the basket";
print "use A and D keys to go around to the left or to the right";
print "use SPACE to shoot";
print "---";

eps = 1e-9; # this is a constant of precision

if(not dbg):
    scene.userspin = 0;
    scene.userzoom = 0;

ball = sphere(pos = vector(0.0, 0.0, 0.0), radius = 2.0, m = 2.0, p = vector(0.0, 0.0, 0.0), picked = false, color = color.green);

basket = ring(pos = vector(0.0, -5.0, 0.0), axis = vector(0.0, 1.0, 0.0), thickness = 0.2, radius = 3.3, color = color.yellow);

board = box(pos = basket.pos + vector(-basket.radius - basket.thickness, 3.0, 0), height = 10.0, width = 16.0, length = 0.1, color = color.white)

init_pos = vector(14.0, -8.0, 0.0);

pwin = vector(-10.0, 30.0, 0.0); # momentum needed to score from initial position

gamestate = 0; # 1 if scored, -1 if missed, 0 if in process / not started

score = 0;
scoremax = 0;

curr_angle = 0.0;
scene.center = init_pos;
scene.forward = basket.pos - scene.center;

def camRot(angle):
    global curr_angle, init_pos, scene;
    curr_angle += angle;
    if(curr_angle > pi / 2.0 + eps):
        curr_angle = pi / 2.0;
    elif(curr_angle + eps < -pi / 2.0):
        curr_angle = -pi / 2.0;
    scene.center = vector(init_pos.x * cos(curr_angle) - init_pos.z * sin(curr_angle),
                          init_pos.y,
                          init_pos.x * sin(curr_angle) + init_pos.z * cos(curr_angle));
    scene.forward = basket.pos - scene.center;
    ball.p = norm(board.pos + vector(0.0,12.0,0.0) - ball.pos) * mag(pwin);

def camMove(speed):
    global curr_angle, init_pos, scene;
    init_pos.x -= speed;
    if (init_pos.x - speed + eps < 1.0):
        init_pos.x = 1.0;
    elif(init_pos.x - speed > 20.0 + eps):
        init_pos.x = 20.0;
    scene.center = vector(init_pos.x * cos(curr_angle) - init_pos.z * sin(curr_angle),
                          init_pos.y,
                          init_pos.x * sin(curr_angle) + init_pos.z * cos(curr_angle));
    scene.forward = basket.pos - scene.center;
    ball.p = norm(board.pos + vector(0.0,12.0,0.0) - ball.pos) * mag(pwin);

def ballToCam():
    global ball;
    ball.pos = scene.center + vector(0.0, -ball.radius, 0.0);

def init():
    global gamestate, init_pos, curr_angle, ball;
    gamestate = 0;
    if not dbg:
        curr_angle = -pi/2 + random() * pi;
    else:
        curr_angle = 0.0;
        init_pos = vector(14.0, -8.0, 0.0);
    camRot(curr_angle);
    camMove(-4.0 + random() * 8.0);
    ball.picked = false;
    ball.color = color.green;
    while 1:
        ballToCam();
        if scene.kb.keys:
            s = scene.kb.getkey();
            if s =='w':
                camMove(0.5);
            if s =='s':
                camMove(-0.5);
            if s =='a':
                camRot(pi/32.0);
            if s =='d':
                camRot(-pi/32.0);
            if s == ' ':
                break;
            
# normal to our basket ring; it is directed toward the center of the ball
vNormal = arrow(pos = vector(0,-5,0), axis = vector(0,2,0), visible = false);

intersect = false; # intersection state

k = 0.8; # this coefficient determines the ratio of momentum retained by the ball after a collision
g = vector(0.0, -9.8, 0.0); # our gravitational acceleration vector
dt = 0.02;

init();

while 1:
    if(gamestate):
        scoremax += 1;
        if(gamestate == 1):
            score += 1;
            print "";
            print "YOU SCORED!";
        else:
            print "";
            print "YOU MISSED";
        print "SCORE: ", score, "/", scoremax;
        init();
    if dbg: # debug mode?
        if intersect:
            rate(1000);
            ball.color = color.red;
            ball.visible = not ball.visible;
            vNormal.visible = true;
        else:
            ball.color = color.green;
            vNormal.visible = false;
            ball.visible = true;
            rate(100);
    else:
        rate(100);
    #camRot(pi/3.0);
    ballnewpos = ball.pos;
    if scene.mouse.events:
        evnt = scene.mouse.getevent();
        if dbg:
            if evnt.drag and evnt.pick == ball and (evnt.ctrl or evnt.alt or evnt.shift):
                ball.picked |= evnt.ctrl | evnt.alt * 2 | evnt.shift * 4;
            elif evnt.drop:
                ball.picked = 0;
    if dbg:
        if ball.picked:
            if ball.picked & 1:
                ballnewpos.x = scene.mouse.pos.x;
            elif ball.picked & (1 << 2):
                ballnewpos.y = scene.mouse.pos.y;
            elif ball.picked & (1 << 4):
                ballnewpos.z = scene.mouse.pos.z;
            ball.p = vector(0.0, 0.0, 0.0);
        else:
            ball.color = color.green;
            ball.visible = true;
    if (dbg and not ball.picked) or not dbg:
        ball.p += g * ball.m * dt;
        ballnewpos = ball.pos + ball.p * dt / ball.m;
    if (abs(ballnewpos.x - basket.pos.x) + eps < ball.radius + basket.radius
      and abs(ballnewpos.y - basket.pos.y) + eps < ball.radius
      and abs(ballnewpos.z - basket.pos.z) + eps < ball.radius + basket.radius):
        # Let us assume the basket ring is just a circle so that
        # there will be no need to intersect a sphere with a torus :)
        r1 = basket.radius;
        # r2 is a radius of a circle which is resulted by intersecting
        # the ball with the plane containing the basket ring
        r2 = (abs(ball.radius ** 2.0 - abs(ball.pos.y - basket.pos.y) ** 2.0)) ** 0.5;
        d = ((ballnewpos.x-basket.pos.x)**2.0+(ballnewpos.y-basket.pos.y)**2.0+(ballnewpos.z-basket.pos.z)**2.0)**0.5;
        if d > r1 + eps: # ball is outside the basket
            intersect = (r1 + r2 > d + eps); # do the ball and the basket intersect?
        else:       # ball is inside the basket
            if r2 > r1 + eps: # ball is bigger than the basket (???)
                xx=1;# do nothing
            elif abs(r1 - r2) < eps: # radii are equal (???)
                xx=1;# do nothing
            elif d + r2 + eps < r1:
                intersect = false;
                if ballnewpos.y < basket.pos.y: # center of the ball had passed through the basket
                    gamestate = 1;
            else: # they intersect inside
                intersect = true;
    else:
        intersect = false;
    if intersect:
        vNormal.pos = basket.pos + r1 * norm(ballnewpos - vector(0.0, ballnewpos.y - basket.pos.y, 0.0) - basket.pos);
        vNormal.axis = norm(ballnewpos - vNormal.pos) * r2;
        if (dbg and not ball.picked) or not dbg:
            ball.p = norm(vNormal.axis) * mag(ball.p) * k;
            ballnewpos = ball.pos + ball.p * dt / ball.m;

    if(ballnewpos.x - board.pos.x + board.length / 2.0 + eps < ball.radius):
        if(ballnewpos.y + eps < board.pos.y + board.height / 2.0
          and ballnewpos.y > board.pos.y - board.height / 2.0 + eps
          and ballnewpos.z + eps < board.pos.z + board.width / 2.0
          and ballnewpos.z > board.pos.z - board.width / 2.0 + eps):
            ball.p.x = -ball.p.x;
            ball.p *= k;
            ballnewpos = ball.pos + ball.p * dt / ball.m;
        else:
            gamestate = -1;

    ball.pos = ballnewpos;
    
    
    if ball.pos.y < -20.0:
        gamestate = -1;
