/*
* 
* Author: Janne Siera
* Author Website: http://www.bbgamedesign.com
* 
* Permission:
* Anyone can use or alter this script for any purpose. However you cannot sell it. If you found this script usefull or want to contribute to 
* improve it and give back to the community please visit http://www.bbgamedesign.com to share your experiences. Also, visit http://www.pbbgsnippets.com to 
* find more resources and to give something back to the community.
* 
* Content:
* This is a very basic javascript script based on the canvas element to display a game map in a browser. I share it with the PBBG community as an 
* example to the absolute beginner and / or anyone else interested in the script. This is not a very optimised script and there is a lot of room 
* for improvement. However, at time of writing (or better, typing) I am planning to write (or again better, type) an article on creating two 
* dimensional game maps for PBBG's including different techniques to implement this sort of map.
* 
* The map doesn't work?
* This map is tested in Google Chrome on windows. It is not tested in any other browser / on any other platform. Yeah... bite me. (and fuck IE too btw)
* 
*/

/*
* Javascript engine for the canvas game map.
* This script can easily be addapted to run with a programming language on the server side by making use of AJAX calls.
*/

// Load the game map on page load (the code is in a javascript namespace)
window.onload = init;
function init() {
	MAP.load();
}

// The game map namespace
var MAP = function() {

// Static game data

/*
* The map data is a multi-dimensional array, since we will have different 'layers' for our map
* The map is a grid with rows and columns forming 'cels'
* The first layer of the map contains out of tiles, every cel must have an id as reference to a tile image
* The second layer is optional (set by the 'exists' parameter) and contains an id as reference to an image if it exists
* If you know how the map works you will see that it's very easy to add an extra layer, don't add too much since this is a very unoptimised script
*/

var map_data =
{
	0 : {
		0 : {
			'tile_img' : 1,
			'item' : { 'exists' : false, 'img' : '', },
		},
		1 : {
			'tile_img' : 2,
			'item' : { 'exists' : false, 'img' : '', },
		},
		2 : {
			'tile_img' : 2,
			'item' : { 'exists' : false, 'img' : '', },
		},
		3 : {
			'tile_img' : 2,
			'item' : { 'exists' : false, 'img' : '', },
		},
		4 : {
			'tile_img' : 2,
			'item' : { 'exists' : false, 'img' : '', },
		},
		5 : {
			'tile_img' : 2,
			'item' : { 'exists' : false, 'img' : '', },
		},
		6 : {
			'tile_img' : 2,
			'item' : { 'exists' : false, 'img' : '', },
		},
		7 : {
			'tile_img' : 2,
			'item' : { 'exists' : false, 'img' : '', },
		},
		8 : {
			'tile_img' : 2,
			'item' : { 'exists' : false, 'img' : '', },
		},
		9 : {
			'tile_img' : 3,
			'item' : { 'exists' : false, 'img' : '', },
		},
	},
	1 : {
		0 : {
			'tile_img' : 8,
			'item' : { 'exists' : false, 'img' : '', },
		},
		1 : {
			'tile_img' : 0,
			'item' : { 'exists' : false, 'img' : '', },
		},
		2 : {
			'tile_img' : 0,
			'item' : { 'exists' : true, 'img' : 17, },
		},
		3 : {
			'tile_img' : 0,
			'item' : { 'exists' : false, 'img' : '', },
		},
		4 : {
			'tile_img' : 15,
			'item' : { 'exists' : false, 'img' : '', },
		},
		5 : {
			'tile_img' : 15,
			'item' : { 'exists' : false, 'img' : '', },
		},
		6 : {
			'tile_img' : 15,
			'item' : { 'exists' : false, 'img' : '', },
		},
		7 : {
			'tile_img' : 15,
			'item' : { 'exists' : false, 'img' : '', },
		},
		8 : {
			'tile_img' : 15,
			'item' : { 'exists' : false, 'img' : '', },
		},
		9 : {
			'tile_img' : 4,
			'item' : { 'exists' : false, 'img' : '', },
		},
	},
	2 : {
		0 : {
			'tile_img' : 8,
			'item' : { 'exists' : false, 'img' : '', },
		},
		1 : {
			'tile_img' : 16,
			'item' : { 'exists' : false, 'img' : '', },
		},
		2 : {
			'tile_img' : 9,
			'item' : { 'exists' : false, 'img' : '', },
		},
		3 : {
			'tile_img' : 0,
			'item' : { 'exists' : false, 'img' : '', },
		},
		4 : {
			'tile_img' : 0,
			'item' : { 'exists' : true, 'img' : 17, },
		},
		5 : {
			'tile_img' : 0,
			'item' : { 'exists' : false, 'img' : '', },
		},
		6 : {
			'tile_img' : 0,
			'item' : { 'exists' : false, 'img' : '', },
		},
		7 : {
			'tile_img' : 15,
			'item' : { 'exists' : false, 'img' : '', },
		},
		8 : {
			'tile_img' : 15,
			'item' : { 'exists' : false, 'img' : '', },
		},
		9 : {
			'tile_img' : 4,
			'item' : { 'exists' : false, 'img' : '', },
		},
	},
	3 : {
		0 : {
			'tile_img' : 8,
			'item' : { 'exists' : false, 'img' : '', },
		},
		1 : {
			'tile_img' : 14,
			'item' : { 'exists' : false, 'img' : '', },
		},
		2 : {
			'tile_img' : 12,
			'item' : { 'exists' : false, 'img' : '', },
		},
		3 : {
			'tile_img' : 0,
			'item' : { 'exists' : false, 'img' : '', },
		},
		4 : {
			'tile_img' : 9,
			'item' : { 'exists' : false, 'img' : '', },
		},
		5 : {
			'tile_img' : 16,
			'item' : { 'exists' : false, 'img' : '', },
		},
		6 : {
			'tile_img' : 16,
			'item' : { 'exists' : false, 'img' : '', },
		},
		7 : {
			'tile_img' : 0,
			'item' : { 'exists' : false, 'img' : '', },
		},
		8 : {
			'tile_img' : 15,
			'item' : { 'exists' : false, 'img' : '', },
		},
		9 : {
			'tile_img' : 4,
			'item' : { 'exists' : false, 'img' : '', },
		},
	},
	4 : {
		0 : {
			'tile_img' : 8,
			'item' : { 'exists' : false, 'img' : '', },
		},
		1 : {
			'tile_img' : 9,
			'item' : { 'exists' : false, 'img' : '', },
		},
		2 : {
			'tile_img' : 16,
			'item' : { 'exists' : false, 'img' : '', },
		},
		3 : {
			'tile_img' : 0,
			'item' : { 'exists' : false, 'img' : '', },
		},
		4 : {
			'tile_img' : 0,
			'item' : { 'exists' : true, 'img' : 18, },
		},
		5 : {
			'tile_img' : 10,
			'item' : { 'exists' : false, 'img' : '', },
		},
		6 : {
			'tile_img' : 10,
			'item' : { 'exists' : false, 'img' : '', },
		},
		7 : {
			'tile_img' : 10,
			'item' : { 'exists' : false, 'img' : '', },
		},
		8 : {
			'tile_img' : 0,
			'item' : { 'exists' : true, 'img' : 17, },
		},
		9 : {
			'tile_img' : 4,
			'item' : { 'exists' : false, 'img' : '', },
		},
	},
	5 : {
		0 : {
			'tile_img' : 8,
			'item' : { 'exists' : false, 'img' : '', },
		},
		1 : {
			'tile_img' : 0,
			'item' : { 'exists' : true, 'img' : 18, },
		},
		2 : {
			'tile_img' : 16,
			'item' : { 'exists' : false, 'img' : '', },
		},
		3 : {
			'tile_img' : 0,
			'item' : { 'exists' : false, 'img' : '', },
		},
		4 : {
			'tile_img' : 9,
			'item' : { 'exists' : false, 'img' : '', },
		},
		5 : {
			'tile_img' : 16,
			'item' : { 'exists' : false, 'img' : '', },
		},
		6 : {
			'tile_img' : 16,
			'item' : { 'exists' : false, 'img' : '', },
		},
		7 : {
			'tile_img' : 16,
			'item' : { 'exists' : false, 'img' : '', },
		},
		8 : {
			'tile_img' : 9,
			'item' : { 'exists' : false, 'img' : '', },
		},
		9 : {
			'tile_img' : 4,
			'item' : { 'exists' : false, 'img' : '', },
		},
	},
	6 : {
		0 : {
			'tile_img' : 8,
			'item' : { 'exists' : false, 'img' : '', },
		},
		1 : {
			'tile_img' : 9,
			'item' : { 'exists' : false, 'img' : '', },
		},
		2 : {
			'tile_img' : 16,
			'item' : { 'exists' : false, 'img' : '', },
		},
		3 : {
			'tile_img' : 0,
			'item' : { 'exists' : false, 'img' : '', },
		},
		4 : {
			'tile_img' : 9,
			'item' : { 'exists' : false, 'img' : '', },
		},
		5 : {
			'tile_img' : 16,
			'item' : { 'exists' : false, 'img' : '', },
		},
		6 : {
			'tile_img' : 16,
			'item' : { 'exists' : false, 'img' : '', },
		},
		7 : {
			'tile_img' : 14,
			'item' : { 'exists' : false, 'img' : '', },
		},
		8 : {
			'tile_img' : 12,
			'item' : { 'exists' : false, 'img' : '', },
		},
		9 : {
			'tile_img' : 4,
			'item' : { 'exists' : false, 'img' : '', },
		},
	},
	7 : {
		0 : {
			'tile_img' : 8,
			'item' : { 'exists' : false, 'img' : '', },
		},
		1 : {
			'tile_img' : 9,
			'item' : { 'exists' : false, 'img' : '', },
		},
		2 : {
			'tile_img' : 15,
			'item' : { 'exists' : false, 'img' : '', },
		},
		3 : {
			'tile_img' : 15,
			'item' : { 'exists' : false, 'img' : '', },
		},
		4 : {
			'tile_img' : 0,
			'item' : { 'exists' : true, 'img' : 18, },
		},
		5 : {
			'tile_img' : 10,
			'item' : { 'exists' : false, 'img' : '', },
		},
		6 : {
			'tile_img' : 10,
			'item' : { 'exists' : false, 'img' : '', },
		},
		7 : {
			'tile_img' : 12,
			'item' : { 'exists' : false, 'img' : '', },
		},
		8 : {
			'tile_img' : 15,
			'item' : { 'exists' : false, 'img' : '', },
		},
		9 : {
			'tile_img' : 4,
			'item' : { 'exists' : false, 'img' : '', },
		},
	},
	8 : {
		0 : {
			'tile_img' : 8,
			'item' : { 'exists' : false, 'img' : '', },
		},
		1 : {
			'tile_img' : 0,
			'item' : { 'exists' : true, 'img' : 17, },
		},
		2 : {
			'tile_img' : 10,
			'item' : { 'exists' : false, 'img' : '', },
		},
		3 : {
			'tile_img' : 10,
			'item' : { 'exists' : false, 'img' : '', },
		},
		4 : {
			'tile_img' : 12,
			'item' : { 'exists' : false, 'img' : '', },
		},
		5 : {
			'tile_img' : 0,
			'item' : { 'exists' : false, 'img' : '', },
		},
		6 : {
			'tile_img' : 0,
			'item' : { 'exists' : false, 'img' : '', },
		},
		7 : {
			'tile_img' : 15,
			'item' : { 'exists' : false, 'img' : '', },
		},
		8 : {
			'tile_img' : 15,
			'item' : { 'exists' : false, 'img' : '', },
		},
		9 : {
			'tile_img' : 4,
			'item' : { 'exists' : false, 'img' : '', },
		},
	},
	9 : {
		0 : {
			'tile_img' : 7,
			'item' : { 'exists' : false, 'img' : '', },
		},
		1 : {
			'tile_img' : 6,
			'item' : { 'exists' : false, 'img' : '', },
		},
		2 : {
			'tile_img' : 6,
			'item' : { 'exists' : false, 'img' : '', },
		},
		3 : {
			'tile_img' : 6,
			'item' : { 'exists' : false, 'img' : '', },
		},
		4 : {
			'tile_img' : 6,
			'item' : { 'exists' : false, 'img' : '', },
		},
		5 : {
			'tile_img' : 6,
			'item' : { 'exists' : false, 'img' : '', },
		},
		6 : {
			'tile_img' : 6,
			'item' : { 'exists' : false, 'img' : '', },
		},
		7 : {
			'tile_img' : 6,
			'item' : { 'exists' : false, 'img' : '', },
		},
		8 : {
			'tile_img' : 6,
			'item' : { 'exists' : false, 'img' : '', },
		},
		9 : {
			'tile_img' : 5,
			'item' : { 'exists' : false, 'img' : '', },
		},
	},
};

// Set these variables manually
var tile_width = 50;
var tile_height = 50;
var map_width = 10 * tile_width; // cols * tile_width
var map_height = 10 * tile_height; // rows * tile_height
var fps = 25; // Frames per second
var speed = 1; // Pixels to move the map per frame

// Where are your images located?
var path = "http://gamemap.bbgamedesign.com/img/";

// The images id's refer to this array
// You can download this tileset for free from http://www.bbgamedesign.com
var SRC = ['base_tile.jpg', '0_edge_tile.jpg', '1_edge_tile.jpg', '2_edge_tile.jpg', '3_edge_tile.jpg', '4_edge_tile.jpg', '5_edge_tile.jpg', '6_edge_tile.jpg', '7_edge_tile.jpg', '0_road_tile.jpg', '1_road_tile.jpg', '2_road_tile.jpg', '3_road_tile.jpg', '4_road_tile.jpg', '5_road_tile.jpg', '0_nature_tile.jpg', '1_nature_tile.jpg', 'city.png', 'waypoint.png'];

// Preload the images
var images = [];
for(var i = 0; i < SRC.length; i++) {
	images[i] = new Image; // create new Image object and add to array
	images[i].src = path+SRC[i]; // assign the .src property of the Image object
}

// Map coordinates on page load
// These are the coordinates on the "whole" map
var x_start = 0;
var y_start = 0;

// Create page element references
function create_element_reference() {
	canvas = document.getElementById('map'); // Reference to the canvas element
	canvas_height = canvas.height; // Get the canvas height
	canvas_width = canvas.width; // Get the canvas width
};

/* Draw the map to the screen */

function draw() {
	ctx = canvas.getContext('2d'); // Get the context
	
	/* Prepare the vars */
	// Canvas coordinates
	// These are the coordinates on the canvas element
	x = 0;
	y = 0;
	
	/* Start and End col */
	// Since our canvas element will be smaller than our total map we will only draw the cels that we are currently viewing (from x_start and y_start)
	// I make sure that, if we are not on the edge, we will draw an extra column and an extra row
	// That way items can be bigger than tiles (but only 3x tile size and height, centered in the midle of it's cel!)
	
	// Start rendering from tile:
	start_col = Math.floor(x_start / tile_width); // The column the left-up corner cel is in
	start_row = Math.floor(y_start / tile_height); // The row the left-up corner cel is in
	
	// Calculate offset
	// When the map starts, for example, in the middle of a tile we will start drawing that tile off the screen a little bit
	offset_x = start_col * tile_width - x_start;
	offset_y = start_row * tile_height - y_start;
	
	// Number of tiles to draw
	// Can differ if we are on the beginning or end of the map
	num_tiles_x = canvas_width / tile_width;
	num_tiles_y = canvas_height / tile_height;
	
	// Render extra rows / cols (?)
	// I already mentioned we render an extra row and column so items can be bigger than tiles, we don't do this on the first and last row / col of the map
	if(num_tiles_x + start_col + 1 < map_width / tile_width) { num_tiles_x++; }
	if(num_tiles_y + start_row + 1 < map_height / tile_height) { num_tiles_y++; }
	
	// Set while parameters
	end_col = num_tiles_x + start_col;
	end_row = num_tiles_y + start_row;
	
	// Render extra rows / cols (?)
	// The order in which we do the checks is important
	if(start_col > 0) { start_col -= 1; offset_x -= tile_width; }
	if(start_row > 0) { start_row -= 1; offset_y -= tile_height; }
	
	/* Draw everything to the canvas */
	draw_tiles(); // First layer
	draw_items(); // Second layer
}

// Render the tiles (base layer)
// We loop through the cels and draw each image to the canvas element, row by row
function draw_tiles() {
	y = offset_y;
	row = start_row;
	while(row <= end_row) {
		x = offset_x;
		col = start_col;
		while(col <= end_col) {
			var img = images[map_data[row][col]['tile_img']]; // tile image
			var x_pos = x;
			var y_pos = y;
			ctx.drawImage(img,x_pos,y_pos); // draw to the context
			
			x += tile_width; // Increment x coord
			col++; // Increment col
		}
		y += tile_height; // Increment y coord
		row++; // Increment row
	}
}

// Render the items (second layer)
// We loop through the cels and if an item exists we draw it to the canvas, centered in the middle of it's cel
// For each item we add some data to an 'EVENTS' array to set up an event listener for the mouse so that item will be clickable
// If you want to, you can also make every tile clickable, but this is the technique I use for clickable areas
var EVENTS = []; // Prepare the global var
function draw_items() {
	EVENTS = []; // Reset when the map is drawn again (movement)
	y = offset_y;
	row = start_row;
	while(row <= end_row) {
		x = offset_x;
		col = start_col;
		while(col <= end_col) {
			if(map_data[row][col]['item']['exists'] == true) {
				var img = images[map_data[row][col]['item']['img']]; // tile image
				var width = img.width; // Get the object width
				var height = img.height; // Get the object height
				var x_pos = x + (tile_width / 2) - (width / 2); // Center the object on the tile (on x axis)
				var y_pos = y + (tile_height / 2) - (height / 2); // Center the object on the tile (on y axis)
				ctx.drawImage(img,x_pos,y_pos); // Draw to the context
				
				var event_data = {'img':img, 'x':x_pos, 'y':y_pos, 'col':col, 'row':row};
				EVENTS.push(event_data); // Add the object id to the list to draw
			}
			
			x += tile_width; // Increment x coord
			col++; // Increment col
		}
		y += tile_height; // Increment y coord
		row++; // Increment row
	}
	
	create_item_events(); // Create the mouse event listeners
}

// Create the event listener for the mouse
function create_item_events() {
	// Change the cursor style when mouseover an object
	canvas.addEventListener('mousemove', func1 = function(e) {
		var x_mouse = e.clientX-canvas.offsetLeft; // Offset on the canvas element
		var y_mouse = e.clientY-canvas.offsetTop; // Offset on the canvas element
		
		this.style.cursor='default'; // Change it back to normal if we are not on an item
		for(id in EVENTS) // Check for each item if we are on it
		{
			var x = EVENTS[id]['x'];
			var y = EVENTS[id]['y'];
			var img = EVENTS[id]['img'];
			var width = img.width;
			var height = img.height;
			if(x <= x_mouse && x_mouse <= x + width && y <= y_mouse && y_mouse <= y + height) {
				this.style.cursor='pointer';
			}
		}
	}, false);
	// Catch onclick event on an item
	canvas.addEventListener('click', func2 = function(e) {
		var x_mouse = e.clientX-canvas.offsetLeft; // Offset on the canvas element
		var y_mouse = e.clientY-canvas.offsetTop; // Offset on the canvas element
		
		for(id in EVENTS) // Check for each item if we are on it
		{
			var col = EVENTS[id]['col'];
			var row = EVENTS[id]['row'];
			
			var x = EVENTS[id]['x'];
			var y = EVENTS[id]['y'];
			var img = EVENTS[id]['img'];
			var width = img.width;
			var height = img.height;
			if(x <= x_mouse && x_mouse <= x + width && y <= y_mouse && y_mouse <= y + height) {
				click_item(col,row); // When clicked on an item, do this function and pas whatever data you want to pas
			}
		}
	}, false);
}

// Remove the clickable area event listeners (re-created in create_object_events())
function destroy_item_events() {
	canvas.removeEventListener('mousemove', func1, false);
	canvas.removeEventListener('click', func2, false);
}

// Function to be called when an item is 'clicked'
function click_item(col,row) {
	document.getElementById('disp').innerHTML = 'You clicked on an object. This object is on row '+row+' and col '+col+', which is a reference to specifc information about this house.';
}

/* Key input */
// Set up an event listener to catch key input (we are using the arrow keys to move the map)
// Initialise the keycode vars
var keycode_left = 37;
var keycode_up = 38;
var keycode_right = 39;
var keycode_down = 40;

// Initialise the (in)active key vars
var right = false;
var left = false;
var up = false;
var down = false;

// Create event listeners to catch user keyboard input
function create_key_input() {
	// When a key is released
	window.addEventListener('keyup', function(e) {
		switch(e.keyCode)
		{
			case keycode_right: right = false; break;
			case keycode_left: left = false; break;
			case keycode_up: up = false; break;
			case keycode_down: down = false; break;
		}
	}, false); // End keyup
	// When a key is pressed
	window.addEventListener('keydown', function(e) {
		switch(e.keyCode)
		{
			case keycode_right: right = true; break;
			case keycode_left: left = true; break;
			case keycode_up: up = true; break;
			case keycode_down: down = true; break;
		}
	}, false); // End keydown
} // End create_key_input()

// Update key input
function update_key_input() {
	if(right == true && map_width - x_start - canvas_width > speed) { // If the key is pressed and if not exceeding map boundaries
		x_start += +speed; // Change the coords
	}
	if(left == true && x_start >= speed) { // If the key is pressed and if not exceeding map boundaries
		x_start += -speed; // Change the coords
	}
	if(up == true && y_start >= speed) { // If the key is pressed and if not exceeding map boundaries
		y_start += -speed; // Change the coords
	}
	if(down == true && map_height - y_start - canvas_height > speed) { // If the key is pressed and if not exceeding map boundaries
		y_start += +speed; // Change the coords
	}
}

/* The game loop */
// We are going to move the game map by using the arrow keys
// Since this is animation we need a 'game loop'
// In this game map I will use a setInterval that checks the game 25 times per second (25 FPS)
// A more advanced explanation of game loops you can find at http://dev.koonsolo.com/7/dewitters-gameloop/

// Start the game loop
function start_gameloop() {
	gameloop_interval = setInterval(function() { gameloop(); },fps/1000); // frames per second / 1000 milliseconds (=1sec)
}

// Stuff to do every frame
function gameloop() {
	update_key_input(); // Checks if a key is pressed and updates x_start & y_start
	if(right == true || left == true || up == true || down == true) { // If a key is pressed
		destroy_item_events(); // Remove the old mouse event listeners to make room for the new
		draw(); // Draw the map again with the new coords
	}
}

// Return from the namespace
return {
	// Load the game
	load : function() {
		create_element_reference(); // Create references for the page elements (needs to be called first!)
		create_key_input(); // Create the event listeners for key input
		draw(); // Draw the map once
		start_gameloop(); // Start the game loop
	},
};
}();