/* A classic computer game
 * Copyright © 2011 Göran Weinholt <goran@weinholt.se>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// Usage is something like this:
// <html>
//   <head>
//     <script type="application/x-javascript" src="stones.js"></script>
//   </head>
//   <body onload="startstones()" bgcolor="black" text="white">
//     <canvas id="stones" width="320px" height="240px" style="position:absolute"></canvas>
//   </body>
// </html>

var screens;

var mouse_active;
var pal;

var background;

var stones;
var stones_w, stones_h;
var stonecount;

var ball;
var ball_x, ball_y;
var ball_dx, ball_dy;
var ball_ox, ball_oy;
var ball_r;

var paddle;
var paddle_x, paddle_y;
var paddle_w;

function reset_ball() {
    ball_r = 6;
    ball = screens.createImageData(ball_r,ball_r);
    ball_x = background.width/2 - 5;
    ball_y = background.height/2 - 5;
    ball_dx = 3.5 * (Math.random()>0.5?1:-1) + Math.random();
    ball_dy = 4 + Math.random();
    drawstone(ball, 0,0,ball_r,ball_r, 0,249,0);
}

function reset_paddle() {
    mouse_active = 0;
    paddle_w = 45;
    paddle_h = 7;
    paddle_x = background.width/2 - 5;
    paddle_y = background.height - 2*paddle_h;
    paddle = screens.createImageData(paddle_w, paddle_h);
    drawstone(paddle, 0,0,paddle_w,paddle_h, 249,9,0);
}

function reset_stones() {
    stonecount = 0
    stones = [];
    stones_w = background.width/32;
    stones_h = Math.floor(0.5*(background.height/16));
    var i = Math.floor(Math.random()*10);
    for (var x = 0; x < stones_w; x++) {
	stones[x] = [];
	for (var y = 0; y < stones_h; y++) {
	    var tmp = screens.createImageData(30,15);
	    var c = pal[(x*y+i++)%pal.length];
	    drawstone(tmp, 0, 0, 30, 15, c[0],c[1],c[2]);
	    stones[x][y] = tmp;
	    stonecount += 1
	}
    }
}

function miss() {
    if (paddle_w <= 5) {
	reset_paddle();
	reset_stones();
	reset_ball();
    } else {
	paddle_w -= 5;
	paddle = screens.createImageData(paddle_w, paddle_h);
	drawstone(paddle, 0,0,paddle_w,paddle_h, 249,9,0);
    }
}

function coll1(x,y) {
    var x = Math.floor(x / 32);
    var y = Math.floor(y / 16);
    if (!stones[x]) return 0;
    ret = !!stones[x][y];
    stones[x][y] = null;
    return ret;
}

function stonecoord(x,y) {
    // Translate from ball coordinates to stone coordinates
    if (ball_dx > 0) x += ball_r;
    if (ball_dy > 0) y += ball_r;
    ret = new Object();
    ret.x = Math.floor(x / 32);
    ret.y = Math.floor(y / 16);
    return ret;
}

function collisions() {
    if (coll1(ball_x,ball_y) || coll1(ball_x+ball_r,ball_y+ball_r)) {
	stonecount -= 1;
	// This isn't perfect, but it mostly works
	var old = stonecoord(ball_ox, ball_oy);
	var now = stonecoord(ball_x, ball_y);
	if (old.y != now.y)
	    ball_dy = -ball_dy;
	else
	    ball_dx = -ball_dx;
	if (stonecount == 0) {
	    // The level has been won. But there's only one level...
	    reset_stones();
	    reset_paddle();
	    reset_ball();
	}
    } else if (ball_dy > 0
	       && ball_y >= background.height-3*paddle_h
	       && ball_y <= background.height-2*paddle_h
	       && ball_x+ball_r >= paddle_x
	       && ball_x <= paddle_x+paddle_w) {
	ball_dy = -ball_dy;
	if (ball_x <= paddle_x+ball_r) {
	    if (ball_dx > 0)
		ball_dx = -ball_dx;
	    else if (Math.abs(ball_dx) < 4)
		ball_dx *= 1.8;
	} else if (ball_x + ball_r/2 >= paddle_x + paddle_w) {
	    if (ball_dx < 0)
		ball_dx = -ball_dx;
	    else if (Math.abs(ball_dx) < 4)
		ball_dx *= 1.8;
	} else if (ball_x >= paddle_x + paddle_w/2 - 2*ball_r 
		   && ball_x <= paddle_w + paddle_w/2 + 2*ball_r
		   && mouse_active && Math.abs(ball_dx) > 2)
	    ball_dx *= 0.4;

    }
}

function computer_paddle() {
    if (mouse_active) return;
    paddle_x = ball_x - ball_r / 2 - paddle_w / 2;
}

function mouse_paddle(ev) {
    if (ev.pageX == ev.layerX || ev.pageX == ev.layerX) return;
    mouse_active = 1;
    // This is problematic if the page is zoomed in.
    if (ev.layerX) {
	paddle_x = ev.layerX;
    } else if (ev.offsetX) {
	paddle_x = ev.offsetX;
    }
    paddle_x -= paddle_w / 2;
}

function update_field() {
    var s = screens;
    var l;

    if (paddle_x + paddle_w > background.width) {
	paddle_x = background.width - paddle_w;
    } else if (paddle_x < 1) {
	paddle_x = 1;
    }

    s.putImageData(background, 0, 0)

    for (var x = 0; x < stones_w; x++) {
	for (var y = 0; y < stones_h; y++) {
	    var stone = stones[x][y]
	    if (stone) {
		s.putImageData(stone, x*32, y*16)
	    }
	}
    }

    s.putImageData(ball, ball_x, ball_y)
    s.putImageData(paddle, paddle_x, paddle_y)
    ball_ox = ball_x;
    ball_oy = ball_y;
    ball_x = ball_x + ball_dx;
    ball_y = ball_y + ball_dy;

    if (ball_y + ball_r >= background.height) {
	reset_ball();
	miss();
    } else if (ball_x + ball_r >= background.width) {
	ball_dx = -ball_dx;
	ball_x = background.width - ball_r;
    } else if (ball_x <= 0) {
	ball_dx = -ball_dx;
	ball_x = 0;
    } else if (ball_y <= 0) {
	ball_dy = -ball_dy;
	ball_y = 0;
    }

    collisions();
    computer_paddle();
}

function drawrect(img, x0, y0, x1, y1, r,g,b) {
    px = img.data
    for (var x = x0; x < x1; x++) {
	for (var y = y0; y < y1; y++) {
	    var i = 4 * (y * img.width + x);
	    px[i] = r
	    px[i+1] = g
	    px[i+2] = b
	    px[i+3] = 255
	}
    }
}

function drawstone(img, x, y, w, h, r,g,b) {
    var c = 3
    drawrect(img, x, y, x+w, y+h,         r*0.8,g*0.8,b*0.8)
    drawrect(img, x, y, x+w-c, y+h-c,     r,g,b)
    drawrect(img, x+c, y+c, x+w, y+h,     r*0.6,g*0.6,b*0.6)
    drawrect(img, x+c, y+c, x+w-c, y+h-c, r*0.8,g*0.8,b*0.8)
}

function startstones() {
    // awk '/[012]/{print "["$1","$2","$3"],"}' < /usr/share/gimp/2.0/palettes/Pastels.gpl
    pal = [[226,145,145],
	   [153,221,146],
	   [147,216,185],
	   [148,196,211],
	   [148,154,206],
	   [179,148,204],
	   [204,150,177],
	   [204,164,153],
	   [223,229,146],
	   [255,165,96],
	   [107,255,99],
	   [101,255,204],
	   [101,196,255],
	   [101,107,255],
	   [173,101,255],
	   [255,101,244],
	   [255,101,132],
	   [255,101,101]]

    screens = document.getElementById("stones").getContext("2d");

    document.onmousemove = mouse_paddle

    background = screens.createImageData(320,240);
    px = background.data;
    for (var i = 0; i < background.width * background.height * 4; i++) {
	px[i] = 0;
    }

    for (var x = 0; x < background.width/16; x++) {
	for (var y = 0; y < background.width/16; y++) {
	    drawstone(background, x*16, y*16, 15, 15,   0,0,0x5c);
	}
    }

    reset_stones();
    reset_paddle();
    reset_ball();
    update_field();
    setInterval("update_field()", 1000/18);
}

