<html 
  xmlns="http://www.w3.org/1999/xhtml"
  xmlns:svg="http://www.w3.org/2000/svg">

	<head>
	<title>Billiards</title>
	<style type='text/css'><![CDATA[
        #body {
            /*
            text-align: center;
            margin-top: 5em;
            */
        }
		svg#board {
			cursor: none;
			border-style: solid;
			border-width: 1px;
		}
        #shot {
            margin-left: 5em;
        }
        .angle {
            margin-left: 5em;
            margin-right: 5em;
        }
        #retry_button {
            margin-right: 5em;
        }
	]]></style>
	</head>
	<body id='body' displayonload=''>
		<svg:svg id='board'>
		</svg:svg>

		<form action="" onsubmit="return false;">
			<!--
			<span>radius, width, height</span><span id='debug1'></span><br/>
            <input type="button" id='shot' onclick="arrow.shot(white)" value='打つ'/>
            -->
            <br/>
			<span class="angle">　　　
            角度 = 
            <input id='angle' onchange="arrow.updateByDegree()" value='0' size="6"/>  °
            </span>
            <input type="button" id='retry_button' onclick="retry()" value='もう一度'/>
		</form>

	<script type='text/javascript'><![CDATA[
        'use strict'
		const svgNS = 'http://www.w3.org/2000/svg';

        // SETTINGS
        const number_of_balls = 2;
        const shot_times = 1;
        const put_trace = true;
        //const debug = false;

        // physical constants
        const time_step = 0.1;
        const v_init    = 100.0;           // 初期速度(最大値)
        const v_stop    = v_init * 0.1;    // 減衰開始速度
        const v_stop2   = v_init * 0.01;   // みなし停止速度
        const stop_time = 200.0;           // 減衰開始から停止みなしまでの時間(millisec)
        const rate = Math.pow( v_stop2/v_stop, time_step/stop_time ); // 減衰係数
        // time_step *  v_init < 2 * min(ball_radius) // すり抜け防止

        // 衝突回数から反発係数を決める
        /*
        reflection_times = 10;
        e_wall = Math.pow( v_stop/v_init, 1/times )
        */

        // 衝突検出のアルゴリズム上、決して衝突しない。衝突とみなす距離。
        //const distance_precision = v_init * time_step * 0.01; // ~ 0.1

        const ball_mass = 100;

        const e_ball = 0.90;   // restitution: 0~1
        const f_ball = e_ball; // friction: 0(strong) ~ 1(weak)
        const e_wall = 0.90;   
        const f_wall = e_wall; 

        // sizes
        const board_width    = 600;
        const board_height   = 300;
        const ball_radius    = 20;
        const hole_radius    = 40;
        const arrow_length   = 100;
        const pointer_radius = ball_radius;
        const mini_pointer_radius = 3

        // positions
        const white_x = board_width  * 1/4;
        const white_y = board_height * 2/4;
        const black_x = board_width  * 3/4;
        const black_y = board_height * 2/4;
        const  blue_x = board_width  * 2/4;
        const  blue_y = board_height * 1/4;

        const ball_distance = 0.001;
        const ball9_x = board_width  * 3/4;
        const ball9_y = board_height * 2/4;
        const  ball_x =  2*ball_radius * Math.cos(radFromDeg(30)) + ball_distance;
        const  ball_y = -2*ball_radius * Math.sin(radFromDeg(30)) - ball_distance;

        // colors
        const pointer_color     = "#00ff00";
        const pointer_opacity   = "0.45";
        const white_trace_color = "#00ff00";
        const black_trace_color = "#000000";
        const blue_trace_color  = "#0000ff";
        const ball_opacity      = "0.75";
        const ball_stroke_color = "#777777";
        const trace_opacity     = "0.6";
        const trace_radius      = "1";
        const hole_color        = "#aaaaaa";
        const hole_opacity      = "0.8";

        const balls_parameter = [
            {x: white_x, y: white_y,
             ball_color:  '#ffffff', ball_opacity:  ball_opacity,
             trace_color: pointer_color, trace_opacity: trace_opacity,
            }, // white
            {x: ball9_x - 2*ball_x, y: ball9_y,
             ball_color:  '#ffff00', ball_opacity:  ball_opacity,
             trace_color: '#ffff00', trace_opacity: trace_opacity,
            }, // #1 yellow
            {x: ball9_x + 2*ball_x, y: ball9_y,
             ball_color:  '#0000ff', ball_opacity:  ball_opacity,
             trace_color: '#0000ff', trace_opacity: trace_opacity,
            }, // #2 blue
            {x: ball9_x -   ball_x, y: ball9_y + ball_y,
             ball_color:  '#ff0000', ball_opacity:  ball_opacity,
             trace_color: '#ff0000', trace_opacity: trace_opacity,
            }, // #3 red
            {x: ball9_x -   ball_x, y: ball9_y - ball_y,
             ball_color:  '#ff00ff', ball_opacity:  ball_opacity,
             trace_color: '#ff00ff', trace_opacity: trace_opacity,
            }, // #4 purple
            {x: ball9_x +   ball_x, y: ball9_y + ball_y,
             ball_color:  '#ffa400', ball_opacity:  ball_opacity,
             trace_color: '#ffa400', trace_opacity: trace_opacity,
            }, // #5 orange
            {x: ball9_x +   ball_x, y: ball9_y - ball_y,
             ball_color:  '#00ff00', ball_opacity:  ball_opacity,
             trace_color: '#00ff00', trace_opacity: trace_opacity,
            }, // #6 green
            {x: ball9_x,            y: ball9_y + 2*ball_radius +ball_distance,
             ball_color:  '#c5956b', ball_opacity:  ball_opacity,
             trace_color: '#c5956b', trace_opacity: trace_opacity,
            }, // #7 brown
            {x: ball9_x,            y: ball9_y - 2*ball_radius -ball_distance,
             ball_color:  '#000000', ball_opacity:  ball_opacity,
             trace_color: '#000000', trace_opacity: trace_opacity,
            }, // #8 black
            {x: ball9_x,            y: ball9_y,
             ball_color:  '#00dddd', ball_opacity:  ball_opacity,
             trace_color: '#00dddd', trace_opacity: trace_opacity,
            }, // #9 stripe
        ];

        const holes_parameter = [
            {x: 0,               y: 0},
            {x: 0,               y: board_height},
            {x: board_width,     y: 0},
            {x: board_width,     y: board_height},
            {x: board_width / 2, y: 0},
            {x: board_width / 2, y: board_height},
        ];

        const hole0_x = 0;
        const hole0_y = 0;

        const hole1_x = board_width;
        const hole1_y = 0;

        const hole2_x = 0;
        const hole2_y = board_height;

        const hole3_x = board_width;
        const hole3_y = board_height;


        // math functions
        function degFromRad(rad) { return rad / (Math.PI / 180) }
        function radToDeg(rad)   { return rad / (Math.PI / 180) }
        function radFromDeg(deg) { return deg * (Math.PI / 180) }
        function degToRad(deg)   { return deg * (Math.PI / 180) }

        function round2(x,n) {
            const d = Math.pow(10,n);
            return Math.round(x*d)/d;
        }

		function distance(point_a, point_b){
			return Math.sqrt( (point_a.x - point_b.x)**2 + (point_a.y - point_b.y)**2 );
		}

        function degToUnitVector(deg) {
            const rad = radFromDeg(deg);
            return [Math.cos(rad), -Math.sin(rad)];
        }

        // dated funcs
        /*
        function inner_product(point_a, point_b) {
            return point_a.x * point_b.x + point_a.y * point_b.y;
        }

		function Board() {
			return document.getElementById('board');
		}

		function Dimensions() {
			const width = parseInt( Board().getAttributeNS(null, 'width') );
			const height = parseInt( Board().getAttributeNS(null, 'height') );
			return [width,height];
		}
        */

        // Class Vector 2D
		function Vector(x,y){
			this.x = x;
            this.y = y;
            this.__angle__ = 0; // radian. used if norm==0.
		}

        Vector.prototype.spawn = function(){
            const vec = new Vector(this.x, this.y);
            vec.__angle__ = this.__angle__;
            return vec;
        }

        Vector.prototype.scale = function(a){
            this.x *= a;
            this.y *= a;
        }

        Vector.prototype.add = function(vector){
            this.x += vector.x;
            this.y += vector.y;
        }

        Vector.prototype.add2 = function(x,y){
            this.x += x;
            this.y += y;
        }

        Vector.prototype.getNorm = function(){
			return Math.sqrt( this.x**2 + this.y**2 );
        }

        Vector.prototype.getNorm2 = function(){
			return this.x**2 + this.y**2;
        }

        Vector.prototype.setNorm = function(len){
            const l = this.getNorm();
            if( l>0 ){
                this.scale( len / l );
            } else {
                this.x =  len * Math.cos(this.__angle__);
                this.y = -len * Math.sin(this.__angle__);
            }
        }

        Vector.prototype.getAngleByRadian = function(){
            return Math.atan2( -this.y, this.x);
        }

        Vector.prototype.getAngleByDegree = function(){
            return degFromRad( this.getAngleByRadian() );
        }

        Vector.prototype.setAngleByRadian = function(rad){
            const len = this.getNorm();
            this.x =  len * Math.cos(rad);
            this.y = -len * Math.sin(rad);
            this.__angle__ = rad;
        }

        Vector.prototype.setAngleByDegree = function(deg){
            this.setAngleByRadian( radFromDeg(deg) );
        }

        Vector.prototype.getUnitVector = function(){
            const vec = this.spawn();
            vec.setNorm(1);
            return vec;
        }

        Vector.prototype.to = function(vector){
			return new Vector(vector.x - this.x, vector.y - this.y);
        }
         
        Vector.prototype.from = function(vector){
			return new Vector(this.x - vector.x, this.y - vector.y);
        }

        Vector.prototype.distance = function(vector){
            return this.to(vector).getNorm();
        }

        Vector.prototype.inner_product = function(vector){
            return this.x * vector.x + this.y * vector.y;
        }

        // Class Vector confined in board
		function VectorInBoard(x,y,r){
            this.x = x;
            this.y = y;
            this.radius = r;
            this.x_min = r;
            this.x_max = board_width - r
            this.y_min = r;
            this.y_max = board_height - r
        }

		VectorInBoard.prototype = new Vector;

		VectorInBoard.prototype.moveTo = function(x, y){
            const dx = x - this.x;
            const dy = y - this.y;

			if( x < this.x_min ){
				this.x = this.x_min;
                this.y = (dy/dx) * (this.x_min - this.x) + this.y; 
			} else if( x > this.x_max ){
				this.x = this.x_max
                this.y = (dy/dx) * (this.x_max - this.x) + this.y; 
			} else {
                this.x = x;
            }

			if( y < this.y_min ){
				this.y = this.y_min;
                this.x = (dx/dy) * (this.y_min - this.y) + this.x; 
			} else if( y > this.y_max ){
				this.y = this.y_max
                this.x = (dx/dy) * (this.y_max - this.y) + this.x; 
			} else {
                this.y = y;
            }
        }

        // Class Arrow or Cue
		function Arrow(x,y){
            this.shot_times = shot_times;
            this.on_shot = true;
            this.on_update = true;
			//this.length     = arrow_length;

            this.start = new VectorInBoard( x, y, ball_radius );
            if( put_trace == true ){
                this.end   = new VectorInBoard( x + arrow_length, y, pointer_radius );
            } else {
                this.end   = new VectorInBoard( x + arrow_length, y, mini_pointer_radius );
            }
            //this.x = this.end.x - this.start.x;
            //this.y = this.end.y - this.start.y;

            this.drawPointer();
            this.drawRay();
		}

		//Arrow.prototype = new Vector;

        Arrow.prototype.drawPointer = function(){
			this.pointer = document.createElementNS(svgNS,'circle');
			this.pointer.setAttributeNS(null,'cx',          this.end.x);
			this.pointer.setAttributeNS(null,'cy',          this.end.y);
			this.pointer.setAttributeNS(null,'r',           this.end.radius);
			this.pointer.setAttributeNS(null,'fill',        pointer_color);
			this.pointer.setAttributeNS(null,'fill-opacity',pointer_opacity);
			this.pointer.setAttributeNS(null,'stroke',          pointer_color);
			this.pointer.setAttributeNS(null,'stroke-opacity',  pointer_opacity);
			board.appendChild( this.pointer );
        }

        Arrow.prototype.drawRay = function(){
			this.element = document.createElementNS(svgNS,'line');
			this.element.setAttributeNS(null,'x1',this.start.x);
			this.element.setAttributeNS(null,'y1',this.start.y);
			this.element.setAttributeNS(null,'x2',this.end.x);
			this.element.setAttributeNS(null,'y2',this.end.y);
			this.element.setAttributeNS(null,'stroke-width',    '1.5');
			this.element.setAttributeNS(null,'stroke',          pointer_color);
			this.element.setAttributeNS(null,'stroke-opacity',  pointer_opacity);
			this.element.setAttributeNS(null,'stroke-dasharray','1');
			board.appendChild( this.element );
        }

        Arrow.prototype.switch_update = function(){
            this.on_update = !this.on_update
        }

        Arrow.prototype.switch_shot = function(){
            this.on_shot = !this.on_shot
        }

		Arrow.prototype.update = function(bx,by){
            this.end.moveTo(bx, by);

			this.pointer.setAttributeNS(null,'cx',this.end.x);
			this.pointer.setAttributeNS(null,'cy',this.end.y);

            if( this.on_update == true ){
                this.element.setAttributeNS(null,'x2',this.end.x);
                this.element.setAttributeNS(null,'y2',this.end.y);

                let deg = this.start.to(this.end).getAngleByDegree();
                if(deg < 0) deg += 360
                deg = round2(deg, 2);
                document.getElementById('angle').value = deg;
                //document.getElementById('angle').innerHTML = deg;
            }
		}

        Arrow.prototype.updateByDegree = function(){
            const deg = parseFloat( document.getElementById('angle').value );
            const rad = radFromDeg(deg);
            const bx = this.start.x + arrow_length * Math.cos(rad);
            const by = this.start.y - arrow_length * Math.sin(rad); 
            this.update(bx, by);
        }

        Arrow.prototype.shot = function(ball){
            if( this.on_shot == true ){
                this.shot_times--;
                this.on_shot = false;
                this.on_update = false;
                board.removeChild( arrow.element );
                if( this.shot_times == 0){
                    board.removeChild( arrow.pointer );
                }
                //delete arrow;

                const deg = parseFloat( document.getElementById('angle').value );
                ball.v.setNorm(v_init);
                ball.v.setAngleByDegree(deg);

                update();
            }
        }

        // Class Ball
		function Ball(x,y,v,deg, r, ball_color, ball_opacity, trace_color, trace_opacity){
			this.radius = r;
            this.trace_color   = trace_color;
            this.trace_opacity = trace_opacity;
            this.mass = Math.pow( this.radius/10, 3 )
            //alert(this.mass)
            //this.mass = 100

            this.x_min = this.radius;
            this.x_max = board_width - this.radius;
            this.y_min = this.radius;
            this.y_max = board_height - this.radius;

			this.x = x;
			this.y = y;

            this.v = new Vector(0,0);
            this.v.setNorm(v);
            this.v.setAngleByDegree(deg);

			this.element = document.createElementNS(svgNS, 'circle');
			this.element.setAttributeNS(null,'cx',           this.x);
			this.element.setAttributeNS(null,'cy',           this.y);
			this.element.setAttributeNS(null,'r',            this.radius);
			this.element.setAttributeNS(null,'fill',         ball_color);
			this.element.setAttributeNS(null,'fill-opacity', ball_opacity);
		    //this.element.setAttributeNS(null,'stroke',       "#ff0000");
			this.element.setAttributeNS(null,'stroke',       ball_stroke_color);
			this.element.setAttributeNS(null,'stroke-width', '1');
			board.appendChild( this.element );
		}

		Ball.prototype = new Vector;
		//Ball.prototype = new VectorInBoard;

		Ball.prototype.motion = function(dt){
            if( this.v.getNorm() < v_stop ) {
                this.v.scale(rate);
            }
            this.add2( this.v.x * dt, this.v.y * dt );
			this.element.setAttributeNS(null,'cx',this.x);
			this.element.setAttributeNS(null,'cy',this.y);
        }

		Ball.prototype.collision_wall = function(){
			if( (this.x <= this.x_min) || (this.x >= this.x_max) ){
				this.v.x *= -e_wall;
				this.v.y *=  f_wall;
			}
			if( (this.y <= this.y_min) || (this.y >= this.y_max) ){
				this.v.x *=  f_wall;
				this.v.y *= -e_wall;
			}
		}

		Ball.prototype.collision_ball = function(ball){
            //if( this.distance(ball) <= this.radius + ball.radius + distance_precision ){
            const n = this.to(ball).getUnitVector();
            const t = new Vector(-n.y, n.x); // rotate 90

            const va_n = n.inner_product(this.v);
            const va_t = t.inner_product(this.v);
            const vb_n = n.inner_product(ball.v);
            const vb_t = t.inner_product(ball.v);

            const ma = this.mass;
            const mb = ball.mass;
            const m = ma + mb;

            const new_va_n = ( (ma - e_ball * mb) * va_n + (1 + e_ball) * mb * vb_n ) / m;
            const new_vb_n = ( (mb - e_ball * ma) * vb_n + (1 + e_ball) * ma * va_n ) / m;
            const new_va_t = ( (ma + f_ball * mb) * va_t + (1 - f_ball) * mb * vb_t ) / m;
            const new_vb_t = ( (mb + f_ball * ma) * vb_t + (1 - f_ball) * ma * va_t ) / m;

            this.v.x = new_va_n * n.x + new_va_t * t.x;
            this.v.y = new_va_n * n.y + new_va_t * t.y;
            ball.v.x = new_vb_n * n.x + new_vb_t * t.x;
            ball.v.y = new_vb_n * n.y + new_vb_t * t.y;
            //}
		}

		Ball.prototype.collision_ball_same_mass = function(ball){
            //if( this.distance(ball) <= this.radius + ball.radius + distance_precision ){
            const n = this.to(ball).getUnitVector();
            const t = new Vector(-n.y, n.x); // rotate 90

            const va_n = n.inner_product(this.v);
            const va_t = t.inner_product(this.v);
            const vb_n = n.inner_product(ball.v);
            const vb_t = t.inner_product(ball.v);

            const new_va_n = va_n - (1 + e_ball) * (va_n - vb_n)/2;
            const new_va_t = va_t - (1 - f_ball) * (va_t - vb_t)/2;
            const new_vb_n = vb_n + (1 + e_ball) * (va_n - vb_n)/2;
            const new_vb_t = vb_t + (1 - f_ball) * (va_t - vb_t)/2;

            this.v.x = new_va_n * n.x + new_va_t * t.x;
            this.v.y = new_va_n * n.y + new_va_t * t.y;
            ball.v.x = new_vb_n * n.x + new_vb_t * t.x;
            ball.v.y = new_vb_n * n.y + new_vb_t * t.y;
            //}
		}

        // 記録タイマー
		Ball.prototype.putTrace = function(color){
			const trace = document.createElementNS(svgNS,'circle');
			trace.setAttributeNS(null,'cx',           this.x);
			trace.setAttributeNS(null,'cy',           this.y);
			trace.setAttributeNS(null,'r' ,           trace_radius);
            if( color ){
                trace.setAttributeNS(null,'fill',         color);
            } else {
                trace.setAttributeNS(null,'fill',         this.trace_color);
            }
			trace.setAttributeNS(null,'fill-opacity', this.trace_opacity);
			board.appendChild( trace );
        }

		Ball.prototype.getTimeCollisionWithWall = function(dt){
            const x = this.x + this.v.x * dt;
            const y = this.y + this.v.y * dt;
            let tx = dt;
            let ty = dt;
			if( x < this.x_min ){
                tx = (this.x_min - this.x)/this.v.x;
			} else if( x > this.x_max ){
                tx = (this.x_max - this.x)/this.v.x;
			}
			if( y < this.y_min ){
                ty = (this.y_min - this.y)/this.v.y;
			} else if( y > this.y_max ){
                ty = (this.y_max - this.y)/this.v.y;
			}

            return Math.min(dt, tx, ty);
        }

		Ball.prototype.getTimeCollisionWithBall = function(dt, ball){
            const a = new Vector(
                this.x + this.v.x * dt,
                this.y + this.v.y * dt);
            const b = new Vector(
                ball.x + ball.v.x * dt,
                ball.y + ball.v.y * dt);
            const d = a.distance(b);
            const d0 = this.radius + ball.radius;
			if( d < d0 ){
                const p = this.to(ball);
                const v = this.v.to(ball.v);
                const pv = p.inner_product(v);
                const p2 = p.getNorm2();
                const v2 = v.getNorm2();
                return (-pv - Math.sqrt(pv**2 - v2 * (p2 - d0**2))) / v2 
			} else {
                return dt
            }
        }

		Ball.prototype.getTimeFall = function(dt, hole){
            const a = new Vector(
                this.x + this.v.x * dt,
                this.y + this.v.y * dt);
            const d = a.distance(hole);
            const d0 = hole.radius;
			if( d < d0 ){
                const p = this.from(hole);
                const v = this.v
                const pv = p.inner_product(v);
                const p2 = p.getNorm2();
                const v2 = v.getNorm2();
                return (-pv - Math.sqrt(pv**2 - v2 * (p2 - d0**2))) / v2 
			} else {
                return dt
            }
        }

        // get the minimum time (and the opponent object) by the next event: collision with a wall, cossision with a ball, fall in a hole.
		Ball.prototype.getTimeByNextEvent = function(dt){
            let tm = dt;
            let op = undefined;
            const tw = this.getTimeCollisionWithWall(dt);
            if( tm > tw ){
                tm = tw;
                op = wall;
            }
            for(const ball of balls){
                const tb = this.getTimeCollisionWithBall(dt,ball);
                if( tm > tb ){
                    tm = tb;
                    op = ball;
                }
            }
            for(const hole of holes){
                const th = this.getTimeFall(dt,hole);
                if ( tm > th ){
                    tm = th;
                    op = hole;
                }
            }
            return {
                time: tm,
                opponent: op
            }
        }

        // Class Hole
		function Hole(x,y){
			this.x = x;
			this.y = y;
			this.radius = hole_radius;

			this.element = document.createElementNS(svgNS,'circle');
			this.element.setAttributeNS(null,'cx',             this.x);
			this.element.setAttributeNS(null,'cy',             this.y);
			this.element.setAttributeNS(null,'r',              this.radius);
			this.element.setAttributeNS(null,'fill',           hole_color);
			this.element.setAttributeNS(null,'fill-opacity',   hole_opacity);
			//this.element.setAttributeNS(null,'stroke',       hole_color);
			//this.element.setAttributeNS(null,'stroke-width', '1');
			board.appendChild( this.element );
		}

		Hole.prototype = new Vector;

        Hole.prototype.fall = function(ball){
            //if( this.distance(ball) <= this.radius + distance_precision ){
                board.removeChild( ball.element );
                if( ball === white && arrow.shot_times>0){
                    board.removeChild( arrow.pointer );
                }
                balls = balls.filter(item => item !== ball);

                /*
                ball.element.setAttributeNS(null,'fill','none');
                ball.element.setAttributeNS(null,'stroke','none');
                ball.v.x = 0;
                ball.v.y = 0;
                ball.radius = -3 * ball_radius;
                */
            //}
        }

        // dummy class
        class Wall {
            constructor(){}
        }

        let timer;
        function update(){

            let t = time_step;
            let next_time = t
            let next_opponent = undefined;
            let next_ball = undefined;

            //let event_times = 0
            while( t > 0 ){
                next_time = t
                for(const ball of balls){
                    const op_tm = ball.getTimeByNextEvent(next_time);
                    if( next_time > op_tm.time ){
                        next_time = op_tm.time
                        next_opponent = op_tm.opponent
                        next_ball = ball
                    }
                }
                //if( event_times > 0) alert(event_times)
                //event_times++;

                for(const ball of balls){
                    ball.motion(next_time);
                }

                if( next_opponent instanceof Wall ){
                    next_ball.collision_wall();
                    next_opponent = undefined;
                    next_ball = undefined;
                } else if( next_opponent instanceof Ball ){
                    next_ball.collision_ball(next_opponent);
                    next_opponent = undefined;
                    next_ball = undefined;
                } else if( next_opponent instanceof Hole ){
                    next_opponent.fall(next_ball);
                    next_opponent = undefined;
                    next_ball = undefined;
                } else {
                    // NO EVENT
                }

                t -= next_time
            }

            if( put_trace == true )
                for(const ball of balls){ ball.putTrace(); }

            let number_of_balls_stopped = 0;
            for(const ball of balls){
                if( ball.v.getNorm() < v_stop2 ){
                    number_of_balls_stopped++;
                    continue;
                } else {
                    timer = setTimeout(update, time_step);
                    break;
                }
            }
            if( number_of_balls_stopped == balls.length ){
                clearTimeout(timer);
                if( white.element.parentNode && arrow.shot_times > 0 ){
                    arrow.start.x = white.x
                    arrow.start.y = white.y
                    arrow.drawRay();
                    arrow.on_shot = true;
                    arrow.on_update = true;
                }
            }
        }

        function retry(){
            arrow.shot_times = shot_times;
            const deg = document.getElementById('angle').value;
            localStorage.setItem('angle', deg);
            window.location.reload();

            /*
            clearTimeout(timer);
            for(const ball of balls){
                board.removeChild( ball.element );
            }
            */
        }

        const board = document.getElementById('board');
        board.setAttributeNS(null, 'width',  board_width);
        board.setAttributeNS(null, 'height', board_height);

        const wall = new Wall();

        const holes = [
            new Hole(hole0_x, hole0_y),
            new Hole(hole1_x, hole1_y),
            new Hole(hole2_x, hole2_y),
            new Hole(hole3_x, hole3_y)
        ];

        const arrow = new Arrow(white_x, white_y);
        let deg = localStorage.getItem("angle");
        document.getElementById('angle').value = deg;
        arrow.updateByDegree(deg);

        let balls = Array(number_of_balls);
        switch( number_of_balls ){
            case 1:
                balls = [ 
                    new Ball(white_x, white_y,  0, 0,  ball_radius,
                             "#ffffff",  ball_opacity, 
                             "#00ff00", trace_opacity),
                ];
                break;
            case 2:
                balls = [ 
                    new Ball(white_x, white_y,  0, 0,  ball_radius,
                             "#ffffff",  ball_opacity, 
                             "#00ff00", trace_opacity),
                    new Ball(black_x, black_y,  0, 0, ball_radius,
                             "#000000",  ball_opacity, 
                             "#000000", trace_opacity),
                ];
                break;
            case 3:
                balls = [ 
                    new Ball(white_x, white_y,  0, 0,  ball_radius,
                             "#ffffff",  ball_opacity, 
                             "#00ff00", trace_opacity),
                    new Ball(black_x, black_y,  0, 0, ball_radius,
                             "#000000",  ball_opacity, 
                             "#000000", trace_opacity),
                    new Ball(blue_x,  blue_y,   0, 0,  ball_radius,
                             "#0000ff",  ball_opacity,
                             "#0000ff", trace_opacity),
                ];
                break;
            case 9:
                for( let i=0; i<=number_of_balls; i++ ){
                    balls[i] = new Ball( 
                        balls_parameter[i].x, balls_parameter[i].y, 
                        0, 0, 
                        ball_radius,
                        balls_parameter[i].ball_color,  balls_parameter[i].ball_opacity,
                        balls_parameter[i].trace_color, balls_parameter[i].trace_opacity,
                    );
                }
                break;
            case 50:
                balls[0] = new Ball(
                    white_x, white_y,  0, 0,  ball_radius,
                    "#ffffff",  ball_opacity, 
                    "#00ff00", trace_opacity
                );

                const x = (Math.random()-0.5) * (board_width * 1/3) + black_x;
                const y = (Math.random()-0.5) * (board_height * 1/2) + black_y;
                const r = Math.random() * 22 + 3;

                balls[1] = new Ball(
                    x, y,
                    0, 0, 
                    r,
                    "#000000", ball_opacity,
                    "#000000", trace_opacity,
                );

                for( let i=2; i<=number_of_balls; i++ ){
                    const x = (Math.random()-0.5) * (board_width * 1/3) + black_x;
                    const y = (Math.random()-0.5) * (board_height * 1/2) + black_y;
                    const r = Math.random() * 22 + 3;

                    let not_overlap = 0;
                    for( let j=1; j<i; j++ ){
                        //alert(`i=${i}, j=${j}`)
                        const xj = balls[j].x;
                        const yj = balls[j].y;
                        const rj = balls[j].radius;
                        if( Math.sqrt( (x - xj)**2 + (y - yj)**2 ) > r + rj ){ 
                            not_overlap++;
                        } else {
                            break;
                        }
                    }

                    if( not_overlap == (i-1) ){
                        balls[i] = new Ball( 
                            x, y,
                            0, 0, 
                            r,
                            "#000000", ball_opacity,
                            "#000000", trace_opacity,
                        );
                    } else {
                        i--;
                    }
                }

                break;
            default:
        }
        const white = balls[0];


        if ( Boolean(window.TouchEvent) ){
            board.addEventListener("touchstart", function(ev){
                const touches = ev.targetTouches;
                const x = touches[0].clientX;
                const y = touches[0].clientY;
                arrow.update(x,y);
            })

            board.addEventListener("touchmove", function(ev){
                ev.preventDefault();
                const touches = ev.targetTouches;
                const x = touches[0].clientX;
                const y = touches[0].clientY;
                arrow.update(x,y);
            })

            board.addEventListener("touchend", function(ev){
                //ev.preventDefault();
            })

            white.element.addEventListener("touchstart", function(ev){
                arrow.shot(white);
            })
        } else {
            //board.onmousemove = function(ev){
            board.addEventListener("mousemove", function(ev){
                //let ev = window.event;
                //document.getElementById('debug5').innerHTML = [ev.clientX, ev.clientY]
                arrow.update(ev.clientX,ev.clientY);
            })

            //window.addEventListener("mousedown", function(ev){
            //document.onmousedown = function(ev){
            //board.onmousedown = function(ev){
            //board.addEventListener("mousedown", function(ev){
            board.addEventListener("click", function(ev){
                arrow.shot(white);

                //arrow.update(ev.clientX,ev.clientY);
                //arrow.update(ev.pageX,  ev.pageY);
                //arrow.update(ev.screenX,ev.screenY);
            })
        }

        document.addEventListener("keydown", function(ev){
            if( ev.key === "Enter" )
                arrow.shot(white);
        });

	]]></script>

	</body>
</html>
