Register Register Member Login Member Login Member Login Forgot Password ??
PHP , ASP , ASP.NET, VB.NET, C#, Java , jQuery , Android , iOS , Windows Phone
 

Registered : 109,027

HOME > Client Script Forum > ตัวอย่างการใช้ javascript เขียนเกมต่อจิ๊กซอว์ ที่สามารถดึงรูปจาก internet มาเล่นได้



 

ตัวอย่างการใช้ javascript เขียนเกมต่อจิ๊กซอว์ ที่สามารถดึงรูปจาก internet มาเล่นได้

 



Topic : 135672



โพสกระทู้ ( 8 )
บทความ ( 0 )



สถานะออฟไลน์




โปรแกรมนี้พิเศษที่สามารถจะเอารูปอะไรก็ได้ จาก internet มา ต่อจิ๊กซอว์ ได้

สำหรับใครที่จะลองเล่นดู
ตอนนี้ยังหา host ลงไม่ได้ เอาใส่ไว้ใน googlesite ชั่วคราวไปก่อน
https://sites.google.com/view/jsxjavascriptexample/jsx/jsx5663

jsx5663demo

สำหรับเพื่อนๆ น้องๆ ที่อยากศึกษาตัวอย่างการเขียน javascript
javascript สามารถทำอะไรได้มากกว่าที่หลายคนคิด
โปรแกรมนี้เขียนด้วย javascript โดยที่โปรแกรมทั้งหมดอยู่ใน jsx5663.html เพียง file เดียว
ผมเอา sourcecode ใส่ไว้ในกรอบข้างล่างนี่แล้ว
(สามารถ copy ไปใส่ text file แล้ว save เป็น jsx5663.html แล้ว open ด้วย google-chrome ได้)

Code (HTML / JavaScript)
// loading
// <script type='text/javascript'>

	var x5663header = "jsx5663.html : Jigsaw Puzzle Game";
	var x5663version = "version x.x last update 29-oct-2020"

	// เกมส์ ต่อจิ๊กซอว์ ที่สามารถใส่ url เพื่อดึงรูปจาก internet มาเล่นได้

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// สารบัญ
	// ภาค 1 : หลักการ
	// ภาค 2 : ตัวแปร และค่าคงที่
	// ภาค 3 : โปรแกรม ส่วน แสดงผล
	// ภาค 4 : โปรแกรมส่วนตอบสนองต่อ mouse
	// ภาค 5 : function ที่ copy มาจาก jsx อื่นๆ

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ภาค 1 : หลักการ
	
	// แนวทางต่างๆในการเขียนโปรแกรม
	// การสร้างรูป jigsaw
	//	ขั้นที่หนึ่ง จะคำนวณก่อนว่าจะตัดรูปออกเป็น m column * n row
	//		ถ้าตัดตาม m * n ก็จะได้เป็นสี่เหลี่ยมผืนผ้า ชิ้นเท่าๆกัน
	//		จะตำแหน่งมุมบนซ้ายของสี่เหลี่ยมผืนผ้านี้ เป็นจุด reference ของ jigsaw แต่ละชิ้น
	//		จุด reference นี้จะเก็บในตัวแปร x5663posx และ x5663posy
	//		ตอนเริ่มต้น
	//			posx = m*ความกว้างของแต่ละชิ้น
	//			posy = n*ความสูงของแต่ละชิ้น
	//		แต่เมื่อมีการใช้ mouse จับแล้วลาก ค่า posx,posy ก็จะเปลี่ยนไป
	//		รายละเอียดการคำนวณว่าจะตัดเป็นกี่ column กี่ row จึงจะได้จำนวนชิ้นที่ต้องการ ดูใน function x5663adjustsize() และ x5663calccut()
	//		
	//	ขั้นที่สอง ทำการ random ค่า dx dy เพื่อให้มุมทั้งสี่ของรูปสี่เหลี่ยมเลื่อนไปจากเดิม
	//		jigsaw แต่ละชิ้นจากขั้นที่แล้ว ที่เป็นสี่เหลี่ยมผืนผ้าขนาดเท่าๆกัน ก็จะกลายเป็นสี่เหลี่ยมด้านไม่เท่า
	//		การเก็บข้อมูลจะเป็นเป็น dx และ dy
	//		โดยจะเก็บเฉพาะ dx และ dy ของจุดมุมบนซ้าย (จุดอื่นดูจากชิ้นทางขวา ชิ้นล่าง และชิ้นขวาล่าง)
	//		ถ้าต้องการตำแหน่ง x,y ของจุดมุม ต้องเอาตำแหน่ง reference + dx,dy จึงจะได้ตำแหน่งมุมบนซ้ายของสี่เหลี่ยมด้านไม่เท่า
	//		และเพื่อให้สามารถเก็บข้อมูล มุมขวา/มุมล่าง/มุมขวาล่าง ของชิ้นสุดท้ายได้ จึงต้องมีชิ้นเสมือนอีก 1 ชิ้นต่อจากชิ้นสุดท้าย
	//		พื้นที่การเก็บข้อมูล dx,dy จะเพิ่มมาเป็น (m+1) * (n+1)
	//
	//		การ random จะ random ให้แนวตัดโย้ไปโย้มาเป็นรูปคลื่น sin สองความถี่
	//		โดยมี amplitude, frequency, และ phase ที่ random
	//		รายละเอียดการ random ดูใน function x5663randomgrid()
	//
	//	ขั้นที่สาม การสร้างเส้น curve jigsaw
	//		ในขั้นตอนนี้จะเปลี่ยนจากรูปสี่เหลี่ยมผืนผ้า ให้เป็นขอบโค้ง มีเดือย มีเว้า
	//		(ไม่แน่ใจว่าส่วนที่โค้วออกมาเป็นติ่งเขาเรียกว่าอะไร ขอเรียกง่าเดือยไปก่อนก็แล้วกัน)
	//		การทำขอบ curve จะทำทีละด้าน
	//		โดยแต่ละด้าน ใช้ข้อมูล 6 จุด คือ a,b,c,d, aa, bb
	//			สมมุติว่าทำขอบบนก่อน
	//			จุด a,b,c,d คือจุดทั้งสี่ของสี่เหลี่ยมด้านไม่เท่า (มุมบนซ้าย,บนขวา,ล่างขวา,ล่างซ้าย)
	//			จุด aa คือ a ของชิ้นทางซ้าย
	//			จุด bb คือ b ของชิ้นทางขวา
	//			ค่า aa และ bb ใช้ในการปรับความเรียบ ให้เส้นขอบของชิ้นข้างๆที่วิ่งเข้ามุม กับเส้นขอบของชิ้นนี้ที่วิ่งออกจากมุมเดียวกัน  มี slope เท่ากัน
	//
	//		การวาด curve เคยทำไว้ครั้งหนึ่งแล้วใน โปรแกรม jsx1284
	//		ใน jsx1248 ใช้ sin/cos curve ในการสร้าง
	//		แต่การคำนวณซับซ้อนเกินไป และไม่ยืดหยุ่น ในโปรแกรมนี้ก็เลยเปลี่ยนมาใช้ bezier curve แทน
	//		ถ้ามีโอกาสจะนำ การสร้าง curve ตรงส่วนนี้ไปเขียนเป็น jsx อีกโปรแกรมหนึ่งสำหรับเปรียบเทียบกับ jsx1284 (แต่ตอนนี้ยังไม่ได้สร้าง)
	//
	//		ตอนนี้ ในแต่ละด้าน ใช้ bezier แบบ control 2 จุด จำนวนสองชุด
	//		รู้สึกว่ายังไม่ดีเท่าไร
	//		อาจจะปรับเปลี่ยนในเร็วๆนี้
	//
	//	การเติมรูปลงไปในเส้นกรอบที่เป็น curve
	//		โชคดีที่ canvas มีคำสั่ง clip
	//		แค่วางแนวเส้นขอบ แล้วสั่ง ctx.clip (คล้ายกับตอนสั่ง fill หรือ stroke)
	//		หลังจาก clip ถ้าสั่ง drawImage ส่วนที่เกินขอบจะถูกตัดทิ้งโดยอัตโนมัติ
	//		คำสั่ง clip สั่งแล้วเอาออกโดยตรงไม่ได้ ต้องสั่ง ctx.save ไว้ก่อน แล้ว ctx.restore เพื่อเอา clip ออก
	//
	//	การแก้ปัญหา redraw ช้า
	//		เนื่องจากมีการคำนวณแยอะมาก ทำให้ redraw ช้า ตอนเอา mouse จับลาก โปรแกรมตอบสนองไม่ทัน
	//		จึงใช้การวาดลงใน template ไว้ก่อน (วาดครั้งเดียว)
	//		แล้วค่อยตัด template มาใส่ในหน้าจอหลัก
	//		การวาดใน template จะเตรียมที่สำหรับแต่ละชิ้นเอาไว้ 2 เท่า ของขนาดก่อน random (ขนาดดั้งเดิมในขั้นที่หนึ่ง)
	//
	// การแก้ไขปัญหาอื่นๆ
	// การแก้ปัญหา ชิ้นที่เอา mouse จับลาก ไม่ได้อยู่ด้านหน้า โดนชิ้นอื่นบัง
	//	แก้ปัญหาโดยเพิ่ม ตัวแปร x5663redraworder
	//	x5663redraworder เป็น one dimension array ซึ่งจะมี jigsaw ทุกชิ้นเป็นสมาชิก
	//	มีโครงสร้างเป็น [[m,n],[m,n],[m,n]...[m,n]]
	//	โดย [m,n] จะชี้ไปยัง jigsaw แต่ละตัว
	//	การ redraw จะวาดตามลำดับใน x5663redraworder
	//	ดังนั้น ชิ้นแรกที่วาดก่อนจะอยู่หลังสุด และชิ้นสุดท้ายจะวาดหลังสุด จะวาดทับชิ้นอื่นๆ และจะอยู่หน้าสุดบนจอ
	//
	//	นอกจาก function scan แล้ว
	//	เมื่อมีการกด mouse โปรแกรมจะมา scan หาชิ้นที่ถูกกดจากใน x5663redraworder ด้วย
	//	โดยเริ่ม scan จากท้ายสุดย้อนมาหาต้นสุด (ถ้ามองจากหน้าจอ ก็คือ scan จากชิ้นบนก่อน แล้วค่อยลึงลงไปหลังจอ)
	//	เมื่อ scan เจอตัวที่ถูก mouse กดแล้ว ก็จะย้ายชิ้นนั้นไปไว้ท้ายสุดของ x5663redraworder
	//	ก็จะทำให้ ชิ้นที่ถูกจับลาก มันจะแสดงผลอยู่บนสุด ไม่โดนใครบัง
	//
	// การจัดการเรื่อง "การดูดติดกัน"
	//	การติดกัน ก็คือ เมื่อเอา mouse ลากชิ้นที่ต่อกันได้ มาวางใกล้กันมากพอ จะเกิดการดูดติดกันโดยอัตโนมัติ
	//	การตรวจสอบจะทำตอนที่ปล่อย mouse ตอนสิ้นสุดการ move
	//	ถ้าตรวจพบ จะวิ่ง (ถูกดูด) เข้าไปติดกัน เป็นก้อนเดียวกัน
	//	เมื่อเกิดการติดกันเป็นก้อน (ตั้งแต่ 2 ชิ้นขึ้นไป) แล้ว
	//		ถ้า select ก้อนใดก้อนหนึ่ง ก็จะต้อง select ทั้งก้อน
	//		ถ้า move ก้อนใดก้อนหนึ่ง ก็ต้อง เคลื่อนไปทั้งก้อน
	//		การป้องกันการตกจอ ก็ต่างกันกรณีก้อนเดียว ป้องกันโดยขอให้มีชิ้นใดชิ้นหนึ่งในกลุ่มยังอยู่ในจอ (ให้เอา mouse จับได้) ก็พอแล้ว
	//
	//	ทำโดย เพิ่มตัวแปรสำหรับเก็บข้อมูล เลขที่กลุ่ม x5663joingroup ให้กับ jigsaw แต่ละชิ้น
	//		ถ้าชิ้นใหนไม่มีกลุ่ม (ยังไม่ได้ติดกับใครเลย)  จะมีเลขที่กลุ่มเป็น -1
	//		สำหรับชิ้นที่เคยดูดติกกับใครบ้างแล้ว จะเอาเลขที่ของชิ้นที่น้อยที่สุดเป็นเลขที่กลุ่ม
	//		เลขที่ชิ้น / เลขที่กลุ่ม จะเอามาจากตำแหน่ง column,row ตามสูตรนี้
	//		เลขที่ชิ้น = เลขที่column * 1000 + เลขที่row
	//
	//		การมี joingroup จะทำให้ เวลาทำอะไรกับ jigsaw ชิ้นใดชิ้นหนึ่ง ต้องคิดเผื่อทำเผื่อไปถึงกลุ่มของมันด้วย
	//			ตอน select ต้องเพิ่มการตรวจสอบ ถ้าสิ่งที่ถูกเลือกมีกลุ่ม ต้องเพิ่มสมาชิกอื่นๆในกลุ่มเข้ามาในการ select ด้วย
	//			ตรงนี้ทำโดย function x5663expandselecttogroup()
	//
	//			ตอน ป้องกันชิ้น jigsaw หลุดออกจากจอ
	//			ถ้ามีกลุ่ม อนุญาติให้ชิ้น jigsaw หลุดออกจากหน้าจอได้ ตราบใดที่ มีสมาชิกในกลุ่มอย้างน้อย 1 ชิ้นที่ยังอยู่ในจอ
	//
	// การป้อนกัน ชิ้น jigsaw หลุดหายไปนอกจอ
	//	ป้องกัน กรณี จับที่ขอบชิ้น แล้วลากไปปล่อยที่ขอบจอ ซึ่งจะทำให้ครั้งต่อไปจะจับยากแล้ว
	//	ป้องกัน กรณี select หลายชิ้น แล้วจับชิ้นใดชิ้นหนึ่งลาก
	//		ตอนที่ลาก ทุกชิ้นที่ถูก select จะ move ไปพร้อมกัน
	//		อาจจะมีบางชิ้นหลุดออกไปจากหน้าจอได้
	//	จะทำการตรวจตอนที่ปล่อย mouse เพื่อสิ้นสุดการ move
	//	ถ้าเจอชิ้นไหนหลุดจอ หรือหวุดหวิดจะหลุดจอ จะทำการเด้งมันกลับมาอยู่ในจอ
	//	สำหรับชิ้นที่มี joingroup (ดูดติดกันเป็นก้อน) สามารถตกจอเป็นบางส่วนได้ โดยต้องมีบางส่วนอยู่ในจอให้เอา mouse จับได้

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ภาค 2 : ตัวแปร และค่าคงที่

	// ค่าคงที่
	var x5663totalpiece = 300; // เลือกว่าจะตัดภาพเป็นกี่ชิ้น
	var x5663pieceratio = 1.2; // กำหนดอัตราส่วนของ jigsaw ที่ตัดแล้ว ว่าจะมีอัตราส่วน กว้างต่อสูง เป็นเท่าไร
	var x5663screenpadding = 10; // เว้นระยะจากขอบจอ ใช้ในหลายๆที่
	var x5663menubutton = [5,5,29,29] ; // ตำแหน่งของ menu ที่มุมบนซ้ายของจอ
	var x5663background = "gray"; // สีพื้น ตอนเริ่มต้น (กดเปลี่ยนตอนเล่นได้)

	// ตัวแปร เก็บขนาดภาพ ขนาดจอ ขนาดชิ้น
	var x5663screensize = [0,0];
	var x5663playsize = [0,0]; // ขนาดของภาพที่ปรับแล้ว สำหรับใช้ในการเล่น
	var x5663cutcount = [1,1]; // ถูกตัดเป็นกี่ชิ้น [แนวตั้ง,แนวนอน]
	var x5663piecesize = [0,0];

	// ตัวแปรเก็บข้อมูลของ jigsaw แต่ละชิ้น
	// เก็บเป็น two dimension array มีขนาด [column][row]
	// ยกเว้น dx,dy มีขนาด [column+1][row+1]
	x5663refx = function(m,n) { return m*x5663piecesize[0]; } // จุด ref point
	x5663refy = function(m,n) { return n*x5663piecesize[1]; } // จุด ref point
	x5663posx = [[ ]]; // ตำแหน่งปัจจุบันของ ตัว jigsaw (ตำแหน่งเริ่มต้นคือ piecewith*column)
	x5663posy = [[ ]]; // ตำแหน่งปัจจุบันของ ตัว jigsaw (ตำแหน่งเริ่มต้นคือ pieceheight*row)
	x5663diffx = [[ ]]; // ค่า x ของมุมบนซ้าย เทียบกับ ref point หรือ pos
	x5663diffy = [[ ]]; // ค่า y ของมุมบนซ้าย เทียบกับ ref point หรือ pos
	x5663pinx = [[ ]]; // เดือยบน 1 หันเข้า -1 หันออก
	x5663piny = [[ ]]; // เดือยซ้าย 1 หันเข้า -1 หันออก
	x5663selected = [[ ]]; // ชิ้นที่ถูก select จะมีค่าเป็น 1, ไม่ถูก select เป็น 0
	x5663joingroup = [[ ]]; // ชื่อกลุ่ม เป็นการบอกว่าชิ้นนี้ดูดติดกับชิ้นอื่นเป็นก้อนแล้ว จะเก็บค่าในรูปแบบ m*1000+n และใช้ชิ้นที่ค่าน้อยที่สุดในกลุ่มเป็นชื่อกลุ่ม

	// ตัวแปรกำหนดการทำงานของโปรแกรม
	// ตัวกำหนดว่าตอน redraw จะแสดงเงาแดงทับชิ้นที่ select หรือไม่
	var x5663showselect = 1; // ถ้าเป็น 1 ตอน redraw จะแสดงเงาแดงบนชิ้นที่ select
		// ปกติถ้ากด select ชิ้นเดียว จะไม่แสดงเงาแดง แต่ถ้า select แบบตีกรอบจะแสดงเงาแดง
		// จะให้ function redraw แสดงเงาแดงหรือไม่ กำหนดที่ตัวแปรนี้

	// ตัวแปรเก็บลำดับการแสดงผล
	var x5663redraworder = [ ]; // โครงสร้างของ orderlist 
		// ตอน redraw จะสั่งวาดตามลำดับ ตัวสุดท้ายใน array อยู่หน้าสุด
		// ตอนค้นหา จะ scan จากหลังสุด ดังนั้น ชิ้นที่อยู่หน้าจะถูกเจอก่อน
		// ข้อมูลที่เก็บใน orderlist จะเป็น จะเป็น [[m1,n1],[m2,n2],[m3,n3],...]
		// ตอนแสดงผล จะวน loop แสดงตามลำดับใน orderlist ดังนั้นตัวสุดท้ายจะอยู่หน้าสุด

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ภาค 3 : โปรแกรม ส่วน แสดงผล

	// โปรแกรมจะแบ่งเป็นสองภาค
	// ภาคที่เกี่ยวกับการตั้งค่า และ แสดงผล (output)
	// ภาคที่เกี่ยวกับการตอบสนองต่อมาส์ (input)

	// quick function
	// ตัวย่อของ function ที่ใช้บ่อย
	//	dw(ข้อความ) : เทียบเท่า document.write(ข้อความ)
	//	ebid(ไอดี) : เทียบเท่า document.getElementById(ไอดี)

	// รายละเอียด function ของภาคตั้งค่า และ แสดงผล
	// x5663mian() : function นี้เป็นโปรแกรมหลัก
	//	ทำหน้าที่เตรียม html element บนหน้าจอ
	//	สั่ง setup
	//	และตั้ง onmouse event เพื่อตอบสนองต่อ mouse
	//
	// x5663buildmenu() : วาด popup menu เตรียมไว้สำหรับให้ user สั่ง newgame สั่งเปลี่ยนรูป สั่งเปลี่ยนสีพื้นหลัง สั่งฯลฯ
	// x5663showmenu() : สั่งให้แสดง popup menu
	// x5663hidemenu() : สั่งซ่อน popup menu
	//
	// x5663changeimage() : สั่งเปลี่ยนรูป ตาม url ที่ user ส่งมา
	//	เนื่องจากไม่สามารถทำงานต่อได้ทันที ต้องรอเวลาใน load รูปจาก internet เสร็จก่อน
	//	จึงตั้ง onload event ไว้ว่า โหลดเสร็จเมื่อไร ให้ไปทำต่อที่ x5663afterchangeimage()
	// x5663afterchangeimage() : function นี้จะเริ่มทำงานเมื่อ โหลดรูปเสร็จ
	//	เป็น function หลักของการ ตั้งค่าเริ่มต้น และแสดงผลครั้งแรก
	//	เป็นศูนย์รวมที่ทำหน้าที่เรียก function ข้างล่างนี้ทั้งหมดมาทำงาน
	//
	// x5663adjustsize() : คำนวณว่าควรจะย่อขยายภาพแค่ไหน ให้เหมาะกับการเล่นบนจอ
	// x5663calccut(numpiece) : คำนวณว่าจะตัดยังไงให้ได้จำนวนชิ้นใกล้เคียงกับที่ user ต้องการ
	// x5663initarray() : สร้างและใส่ค่าเริ่มต้นให้กับ two dimension array ที่จะใช้เก็บข้อมูลของ jigsaw แต่ละชิ้น
	// x5663initredraworder() : ตั้งค่าเริ่มต้นของลำดับการแสดงผล
	// x5663randomdgid() : random แนวตัดให้โย้ไปเย้มา
	// x5663randomdpin() : random ว่าเดือยจะหันเข้าหรือออก
	// x5663resetposition() : กำหนดตำแหน่งให้ jigsaw วางเรียงกันเป็นรูปภาพ
	// x5663shuffleposition() : สลับตำแหน่งให้วางมั่ว
	// x5663redraw() : ทำการวาด jigsaw ทุกชิ้นตามลำดับในตัวแปร x5663redraworder
	//
	// x5663createtemplate() : สร้าง template เพื่อความรวดเร็วในการ redraw
	//	template จะมีขนาด ยาวสามเท่า และสูงสามเท่าของ screen
	//	พื้นที่ที่จัดสรรให้ jigsaw แต่ละชิ้นก็จะมีขนาด กว้างสามเท่า สูงสามเท่าของ piecesize เช่นเดียวกัน
	//	พื้นที่ 3x3 เท่าจะแบ่งเป็น 9 ช่อง
	//	jigsaw แต่ละชิ้นจะถูกวาดในช่องกลางของพื้นที่ 3x3 นั้น
	//	ส่วนช่องรอบๆอีกแดช่อง แปดด้านรอบตัว จะเผื่อสำหรับส่วนโย้เย้และเดือยที่เกินออกมา
	// x5663jigsawborder() : เป็น function ย่อยของ x5663createtemplate
	// x5663jigsawcurve(ctx,aa,a,b,bb,d,c) : เป็น function ย่อยของ x5663createtemplate

	// โปรแกรมที่เกี่ยวกับการตอบสนองต่อมาส์ จะกล่าวโดยละเอียดในภาคต่อไป
	// ณ ที่นี้ เอารายชื่อ function มาบรรยายคร่าวๆก่อน
	// x5663onmouse() : เป็นโปรแกรมหลักของภาคการตอบสนอง
	//	ใน x5663main ได้ตั้ง event เอาไว้แล้ว ว่า
	//	ถ้ามีการ กดเมาส์ ลากเมาส์ ปล่อยเมาส์ ให้กระโดดมาทำงานที่นี่
	//
	// x5663findsinglepiece() : ทำการตรวจว่าตำแหน่ง x,y ที่กดเมาส์ มี jigsaw ชิ้นไหนอยู่หรือไม่
	//	การตรวจสอบจะ scan ตามลำดับที่อยู่ใน x5663redraworder แต่จะเริ่มค้นจากท้ายไปหาต้น
	//	ถ้าเจอ จะหยุดแค่ชิ้นแรก แล้ว return สิ่งที่เจอมาเป็น foundlist
	//	foundlist มีโครงสร้างเป็น [[m,n]] ซึ่งเป็นโครงสร้างเดียวกันกับของ x5663redraworder
	//	foundlist ที่ return จาก function นี้จะมีสมาชิกตัวเดียว หรือถ้าหาไม่เจอก็ไม่มีเลย
	// x5663findmultipiece() : ทำการค้นหาในกรอบ x1y1 .. x2y2 ว่ามี jigsaw ชิ้นไหนอยู่บ้าง
	//	การค้นหาจะไม่หยุดเมื่อเจอ แต่จะ scan ต่ไปจนหมด
	//	และ return ทั้งหมดที่เจอมาใน foundlist
	//
	// x5663selectpiece(piecelist) : ยกเลิกการ select เดิม และ select ใหม่ตาม piecelist
	//	การ select หรือ noselect ของ jigsaw แต่ละชิ้นจะเก็บอยู่ในตัวแปร x5663selected
	//	piecelist เป็นรายชื่อว่าจะให้ select ชิ้นไหนบ้าง
	//	โครงสร้างของ piecelist จะเป็น [[m,n],[m,n],...] ซึ่งเป็นรูปแบบเดียวกับ foundlist และ x5663redraworder
	// x5663getselectlist() : ทำตรงข้ามกับ x5663selectpiece คือจะสร้าง piecelist ที่มีสมาชิกเป็น ชิ้นที่กำลัง select อยู่
	// x5663expandselecttogroup() : ปรับปรุงการ select ในกรณีที่มีการดูดติดกัยเป็นก้อน
	//	ถ้า select ชิ้นใดชิ้นหนึ่งในก้อน จะต้อง select ทุกชิ้นในก้อนด้วย
	// x5663bringselecttofront() : ทำการแก้ไข x5663redraworder โดยการย้ายทุกชิ้นที่ select มาไว้ท้ายสุด
	//	สาม function นี้คือ x5663selectpiece, x5663expandselecttogroup, x5663bringselecttofront มักจะทำต่อกันแบบประมาณว่า
	//		ค้นด้วย find จะ find เดี่ยวหรือ find หมู่ ก็แล้วแต่
	//		ถ้าเจอ ก็ select มัน
	//		select มันแล้วก็ select เพื่อนมันด้วย
	//		แล้วไอ้ที่ select ทั้งหมด จงมาอยู่ด้านหน้าซะ
	//
	// x5663checkjoinable() : ใช้ตอนเอา mouse ลากมาปล่อย
	//	จะทำการตรวจว่า ตรงที่เอามาปล่อยนั้น มีชิ้นที่ต่อกันได้อยู่ตรงนั้นพอดีหรือเปล่า
	//	ถ้ามีชิ้นที่ต่อกันได้อยูใกล้พอ จะวิ่งเข้าไปดูดติดโดยอัตโนมัติ
	// x5663joinpiece() : function ย่อยของ x5663checkjoinable
	//
	// x5663moveselectpiece(dx,dy) : ทำการ move ทุกชิ้นที่ select อยู่
	// x5663movebackfromoutscreen() : move ทุกชิ้นที่ตกจอ ให้กลับมาอยู่ในจอ

	// เข้าโปรแกรมจริงๆสักที ที่แล้วมามีแต่คำบรรยาย
	// โปรแกรมหลักเริ่มตรงนี้
	function x5663main()
	{
		dw = function(x) { for (var i=0; i<arguments.length; i++) { document.write(arguments[i]); } }
		ebid = function(id) { return document.getElementById(id); }
		
		x5663buildscreen()
		x5663buildpopupmenu();
		x5663showmenu();
		x5663changeimage("https://wiki.m-culture.go.th/wikipedia/images/thumb/f/f6/Krathong2.jpg/720px-Krathong2.jpg");
	}

	function x5663buildscreen()
	{
		// เตรียมหน้าจอ เตรียม popup menu และสั่งวาดครั้งแรก
		// หน้าจอจะประกอบไปด้วย
		//	1 testscreen (hidden) : ใช้สำหรับวัดขนาดของ screen
		//	2 canvas1 : ใช้แสดงผลหลัก
		//	3 popupmenu : ใช้ทำ popup menu
		//	4 img1 (hidden) : สำหรับเก็บรูปภาพ
		//	5 template1 (hidden) : เป็น drawing template จะวาดจิ๊กซอว์แบบห่างๆ ไว้ที่นี่ก่อน แล้วตอนจะใช้จริงค่อย cut ไป paste
		//	6 template2 (hidden) : เป็น drawing template เหมือนกัน แต่มีเฉพาะแรเงาสีแดง ใช้ทำสีแดงเวลากด mouse select
		// ตอนที่เข้าโปรแกรมครั้งแรก จะทำการโหลดรูปภาพเริ่มต้น และ setup ค่าเริ่มต้นต่างๆ
		// แล้ว แสดง popup menu
		// บน menu จะมีปุ่ม continue, ปุ่ม restart, ถาดสีให้เลือกพื้นหลัง, และช่องให้ input url ของรูปภาพ
		//	ถ้ากด continue จะ hide menu แล้วให้เล่นต่อ
		//	ถ้ากด restart จะ shuffle ก่อนแล้วทำเหมือน continue

		dw("<div id='testscreen' style='position:absolute; left:0px; top:0px; width:100%; height:100%; overflow:scroll;'></div>");
		var screen = ebid("testscreen");
		var sw = screen.offsetWidth;
		var sh = screen.offsetHeight;
		x5663screensize[0] = sw;
		x5663screensize[1] = sh;

		dw("<div style='position:absolute; left:0px; top:0px; width:100%;'>");
		dw("<canvas id='canvas1' width='"+sw+"' height='"+sh+"' style='background-color:"+x5663background+";'");
		dw(" onmousedown='x5663onmouse(event);'");
		dw(" onmouseup='x5663onmouse(event);'");
		dw(" onmousemove='x5663onmouse(event);'");
		dw(" onmouseout='x5663onmouse(event);'");
		dw("></canvas>");
		dw("<div>");

		dw("<div id='popupmenu' style='position:absolute; left:0px; top:0px; width:100%; height:100%; z-order:-1;'></div>");
		dw("<img id='img1' style='display:none;'></img>");
		dw("<canvas id='template1' width='"+sw*3+"' height='"+sh*3+"' style='display:none;'></canvas>")
		dw("<canvas id='template2' width='"+sw*3+"' height='"+sh*3+"' style='display:none;'></canvas>")
	}

	function x5663showmenu() { ebid("popupmenu").style.display = "block"; }
	function x5663hidemenu() { ebid("popupmenu").style.display="none"; }
	function x5663buildpopupmenu()
	{
		// ใน popup menu ประกอบด้วย
		//	เส้นสามขีดเหมือนที่วาดใน canvas
		//	ไตเติ้ล
		//	ปุ่ม continue และ ปุ่ม restart
		//	แถบเลือก background color
		//	ช่องสำหรับป้อน url ของรูปภาพ
		//	ตัวเลือก รูปภาพแนะนำ
		var div1 = ebid("popupmenu");
		var html = "";
		html += "<div style='width:400px; margin:5px; font-family:sans-serif; line-height:150%;'>";
		html += "<div style='height:"+x5663menubutton[3]+"px; width:"+x5663menubutton[2]+"px; border-radius: 8px; background-color:pink;' onclick='x5663hidemenu()'></div>"
		html += "<div style='border-radius: 8px; margin-left:15px; margin-top:"+(15-x5663menubutton[3])+"px; padding:16px; background-color:pink;'>"

		html += x5663header+"<br>";
		html += "<span style='font-size:70%'>"+x5663version+"</span><hr>";
		html += " <input type='button' value='Continue' style='font-size:150%;4px;padding:8px;' onclick='x5663hidemenu()'>";
		html += " <input type='button' value='Restart' style='font-size:150%; padding:8px;' onclick='x5663shuffleposition(); x5663redraw(); x5663hidemenu()'>";
		html += "<hr>";

		html += "Change Background Color :<br>";
		var colorlist = ["white","lightgray","gray","rgb(60,60,60)","black","lightgreen","darkgreen","rgb(100,100,0)","brown","rgb(0,0,100)"];
		for (var i=0; i<colorlist.length; i++) {
			html += ("<div style='display:inline-block; height:20px; width:20px; margin:1px; border:1px solid black;background-color:"+colorlist[i]+";vertical-align:bottom;' onclick='ebid(\"canvas1\").style.backgroundColor=\""+colorlist[i]+"\";'>&nbsp;</div>");
		}
		html += "<hr>";

		html += "Change Picture :<br>";
		html += "Enter Picture's URL Address<br>";
		html += "<input id='input1' onchange='x5663changeimage(this.value,2);' onclick='this.select();' value='' style='width:100%; font-size:100%;'>";
		html += "<br><br>";

		html += "<div id='optionchoice' style='height:200px; padding-right:8px; overflow-y:scroll; overflow-x:hidden;'><table style='border-collapse: collapse;'>";
		var showicon = function(imageurl) { html+=("<tr style='cursor:pointer; vertical-align:middle;' onclick='ebid(\"input1\").value = \""+imageurl+"\"; x5663changeimage(\""+imageurl+"\");'><td><img src='"+imageurl+"' style='width:40px;'></img></td><td style='border-bottom:1px solid gray;'><div style='width:285px; white-space:nowrap; overflow:hidden;'>&nbsp;"+imageurl+"</div></td></tr>"); }

		showicon("https://reallifephuket.com/wp-content/uploads/2018/11/shutterstock_743218948.jpg"); // ลอยกระทง
		showicon("https://wiki.m-culture.go.th/wikipedia/images/thumb/f/f6/Krathong2.jpg/720px-Krathong2.jpg"); // ลอยกระทง
		showicon("https://www.thairath.co.th/media/dFQROr7oWzulq5FZXVUK42uEIRwbPWdxRixIqRBNazlcaMObgV6YyLc79ror9ZtSUTB.jpg"); //
		showicon("https://i2.wp.com/travelblog.expedia.co.th/wp-content/uploads/2018/07/cover-sea.jpg?resize=1140%2C550&ssl=1"); // ริมทะเล
		showicon("https://cache.gmo2.sistacafe.com/images/uploads/content_image/image/776748/1539069917-24178132_1034348476705567_1562108111253143552_n.jpg"); // ทางเดินในสวน
		showicon("https://i.pinimg.com/originals/72/14/39/7214391977a7bdcf42a0d7bb39fbbb48.jpg"); // ต้นไม้สูง ในหมอก
		showicon("https://i.imgur.com/uOkwbUT.jpg"); // ต้นไม้ ตะใคร่น้ำ
		showicon("https://jjgirls.com/japanese/suzuka-ishikawa/7/suzuka-ishikawa-9.jpg"); // suzuka ishikawa
		html += "</table></div>";

		html += "</div></div>";
		div1.innerHTML = html;
		div1.style.display = "visible";
	}

	function x5663changeimage(imageurl)
	{
		var img1 = ebid("img1");
		img1.src = imageurl;
		img1.setAttribute("onload","x5663afterchangeimage();");
		// ยังทำอะไรต่อไม่ได้ ต้องรอให้โหลดเสร็จ ค่อยทำต่อ
		// ตั้ง onload event ให้ไปทำต่อที่ x5663afterchangeimage()
	}

	function x5663afterchangeimage()
	{
		x5663adjustsize();
		x5663calccut();
		x5663initarray();
		x5663initredraworder();
		x5663randomgrid();
		x5663randompin();
		x5663resetposition();
		x5663createtemplate();
		x5663redraw();
	}


	function x5663adjustsize()
	{
		// จะปรับขนาดให้ใหญ่ที่สุดโดยที่ ไม่ตกขอบ ไม่กินพื้นที่เกินครึ่ง
		// หาค่าสูงสุดของ ความกว้าง ความสูง และ พื้นที่
		var cv1 = ebid("canvas1");
		var maxwidth = cv1.width - x5663screenpadding*2;
		var maxheight = cv1.height - x5663screenpadding*2;
		var maxarea = maxwidth * maxheight * 0.5;

		// ขนาดของรูปภาพ
		var img1 = ebid("img1");
		var w = img1.width;
		var h = img1.height;

		// ทำการปรับขนาด
		// โดยเลือกจาก scale1 scale2 scale3
		var scale1 = maxwidth/w; // scale ให้ความกว้างพอดี
		var scale2 = maxheight/h; // scale ให้ความสูงพอดี
		var scale3 = Math.sqrt(maxarea/(w*h)); // scale ให้พื้นที่พอดี
		var minscale = Math.min(scale1,scale2,scale3); // เลือกแบบที่เล็กสุด
		x5663playsize = [w*minscale,h*minscale];
	}

	function x5663calccut(numpiece)
	{
		// คำนวณว่า ถ้าจะตัดรูปภาพเป็น numpiece ชิ้น จะต้องตัดแนวนอน แนวตั้ง อย่างละกี่แนว
		// โดยกำหนดว่า แต่ละชิ้นที่ตัดแล้วจะมีอัตราส่วน กว้างต่อยาว ใกล้เคียง x5663pieceratio
		if (numpiece == undefined) { numpiece = x5663totalpiece; }

		// หาพื้นที่ของ image ก่อน
		// หารด้วยจำนวนชิ้น จะได้ พื้นที่ของแต่ละชิ้น
		// แก้สมการ
		//	กว้าง * สูง = พื้นที่
		//	(สูง * อัตราส่วนกว้างต่อสูง) * สูง = พื้นที่
		//	สูง * สูง = พื้นที่ / อัตรส่วนกว้างต่อสูง
		var imagewidth = x5663playsize[0];
		var imageheight = x5663playsize[1];
		var imagearea = imagewidth * imageheight;
		var piecearea = imagearea / numpiece;
		var pieceheight = Math.sqrt(piecearea / x5663pieceratio);
		var piecewidth = pieceheight * x5663pieceratio;
		piecewidth = Math.floor(piecewidth);
		pieceheight = Math.floor(pieceheight);

		// จำนวนแนวตัด = ขนาดของภาพ / ขนาดของแต่ละชิ้น
		x5663cutcount = [Math.floor(imagewidth / piecewidth),Math.floor(imageheight / pieceheight)];
		x5663piecesize = [piecewidth,pieceheight];
		x5663playsize = [piecewidth * x5663cutcount[0],pieceheight * x5663cutcount[1]];
	}

	function x5663initarray()
	// initial ตัวแปรที่เป็น two dimension array
	// เตรียมโครงสร้าง array สองชั้น และใส่ค่าเริ่มต้น
	// ต้องทำหลังจาก คำนวน cutcount ก่อน เพื่อที่จะได้รู้ว่าต้องใช้ array ขนาดเท่าไร
	// เพื่อความง่าย กำหนดเผื่อเป็น [column+1][row+1] ทุกตัวไปเลย
	// ความจริงมีแค่ diff กับ pin เท่านั้นที่ใช้ถึง [column+1][row+1] ตัวอื่นๆ ใช้แค่ [column][row] เท่านั้น
	{
		for (var m=0; m<=x5663cutcount[0]; m++) {
			x5663posx[m] = [ ];
			x5663posy[m] = [ ];
			x5663diffx[m] = [ ];
			x5663diffy[m] = [ ];
			x5663pinx[m] = [ ];
			x5663piny[m] = [ ];
			x5663selected[m] = [ ];
			x5663joingroup[m] = [ ];
			for (var n=0; n<=x5663cutcount[1]; n++) {
				x5663posx[m][n] = x5663piecesize[0]*m;
				x5663posy[m][n] = x5663piecesize[1]*n;
				x5663diffx[m][n] = 0;
				x5663diffy[m][n] = 0;
				x5663pinx[m][n] = 1;
				x5663piny[m][n] = 1;
				x5663selected[m][n] = 0;
				x5663joingroup[m][n] = -1;
			}
		}
	}

	function x5663initredraworder()
	// สร้าง list สำหรับเรียงลำดับว่า ชิ้นไหนอยู่หน้า ชิ้นไหนอยู่หลัง
	{
		x5663redraworder = [ ];
		for (var m=0; m<x5663cutcount[0]; m++) {
		for (var n=0; n<x5663cutcount[1]; n++) {
			x5663redraworder.push([m,n]);
		} }
	}

	function x5663randomgrid()
	{
		// random แนวตัดให้เป็นเส้นโค้ง โย้ไปโย้มา
		// แต่ละแนวเป็น sin + cos โดยที่ amplitude และ frequency จะมาจากการ random
		// ไม่ต้องเพิ่ม random phase เพราะการเอา sin + cos จะเกิด phase shift อยู่แล้ว

		// ค่า parameter สำหรับ กำหนดความโย้เย้
		// maxswing เป็นตัวบอกว่าเส้นจะโย้เย้ไปได้สูงสุดไม่เกิน กี่เท่าของ ขนาดชิ้น
		// maxfrequency เป็นตัวบอกว่า ตั้งแต่ต้นถึงปลายเส้น จะให้ส่ายกลับไปกลับมาได้ไม่เกินกี่รอบ
		var maxswing = 0.25; 
		var maxfrequency = 3;

		// เตรียม function ย่อย สำหรับช่วย random
		// เพื่อให้ ใหนๆก็จะเส้นโค้งแล้ว ก็โค้งให้มากไปเลย ไม่มีโค้งน้อย
		// จะปรับ random ปกติ ให้เป็น
		//	random1() จะให้ค่าจาก 0.5 ถึง 1
		//	random2() จะให้ค่าจาก -1 ถึง -0.5 หรือ 0.5 ถึง 1 (หรือพูดได้อีกอย่างว่า ให้ค่าจาก -1 ถึง 1 แต่จะเว้นค่าช่วงกลางที่อยู่ระหว่าง -0.5 ถึง 0.5 ทิ้งไป)
		var random1 = function() { return (Math.random(0.5)+0.5); }
		var random2 = function() { var k = Math.random(1); if (k<0.5) { k -= 1; } return (k); }
		var twopi = 2*Math.PI;

		// วน loop สองรอบ สำหรับแนวตั้งและแนวนอน
		// เส้นโค้งแต่ละเส้นจะสร้างจาก a1*sin(f1) + a2*cos(f2)
		// โดยที่ amplitude และ frequency ของ sin + cos จะ random ของใครของมัน
		// ไม่ต้อง random pahse เพราะ sin + cos จะเกิด phase shift ในตัวอยู่แล้ว

		var maxm = x5663cutcount[0];
		var maxn = x5663cutcount[1];
		var piecewidth = x5663piecesize[0];
		var pieceheight = x5663piecesize[1];

		// ทำแนวนอนก่อน
		for (var n=1; n<maxn; n++) {
			// แต่ละเส้น จะใช้ค่า random 4 ค่าคือ a1,a2,f1,f2
			var a1 = pieceheight * maxswing/2 * random2();
			var a2 = pieceheight * maxswing/2 * random2();
			var f1 = twopi/maxm * maxfrequency * random1();
			var f2 = twopi/maxm * maxfrequency * random1();
			for (var m=0; m<=maxm; m++) {
				// a1*sin(f1) + a2*cos(f2)
				x5663diffy[m][n] = a1*Math.sin(m*f1) + a2*Math.cos(m*f2);
			}
		}

		// ทำแนวตั้ง
		for (var m=1; m<maxm; m++) {
			var a1 = piecewidth * maxswing/2 * random2();
			var a2 = piecewidth * maxswing/2 * random2();
			var f1 = twopi/maxn * maxfrequency * random1();
			var f2 = twopi/maxn * maxfrequency * random1();
			for (var n=0; n<=maxn; n++) {
				x5663diffx[m][n] = a1*Math.sin(n*f1) + a2*Math.cos(n*f2);
			}
		}
	}

	function x5663randompin()
	{
		// random เดือยของ jigsaw แต่ละชิ้น ว่าจะให้เว้าเข้าหรือนูนออก
		for (var m=0; m<x5663cutcount[0]; m++) {
		for (var n=0; n<x5663cutcount[1]; n++) {
			x5663pinx[m][n] = Math.random(1)>0.5?1:-1;
			x5663piny[m][n] = Math.random(1)>0.5?1:-1;
		} }
	}

	function x5663resetposition()
	{
		var marginx = x5663screensize[0]-x5663playsize[0]-x5663screenpadding;
		var marginy = x5663screensize[1]-x5663playsize[1]-x5663screenpadding;
		var maxm = x5663cutcount[0];
		var maxn = x5663cutcount[1];
		var piecewidth = x5663piecesize[0];
		var pieceheight = x5663piecesize[1];
		for (var m=0; m<maxm; m++) {
		for (var n=0;n<maxn; n++) {
			x5663posx[m][n] = piecewidth * m + marginx;
			x5663posy[m][n] = pieceheight * n + marginy;
		} }
	}

	function x5663shuffleposition()
	{
		var padding = 1.2; // spacing เป็นกี่เท่าของ width/height 
		var margin = 0.25; // margin เป็นที่เท่าของ width,height

		// sortlist สำหรับ random ตำแหน่ง
		// เป็น array of [index,m,n]
		// โดย index จะเป็นค่า random
		var sortlist = [ ];
		for (var m=0; m<x5663cutcount[0]; m++) {
		for (var n=0; n<x5663cutcount[1]; n++) {
			x5663selected[m][n] = 0; // ล้างการ selected
			x5663joingroup[m][n] = -1; // ล้าง joingroup
			sortlist.push([Math.random(1),m,n]); // สร้าง sortlist โดยใช้ random index
		} }

		// random โดยการ sort random index
		sortlist.sort(function(a,b) { return a[0]-b[0]});

		// นำ jigsaw ที่ random ลำดับแล้ว มาวางเรียงกัน
		// คำนวนว่าจะวางห่างกันแค่ไหน
		// และวางได้บรรทัดละกี่ชิ้น
		var spacex = Math.round(x5663piecesize[0]*padding);
		var spacey = Math.round(x5663piecesize[1]*padding);
		var maxx = Math.floor((x5663screensize[0]-x5663screenpadding*2)/spacex);
		var maxy = Math.floor((x5663screensize[1]-x5663screenpadding*2)/spacey);
		var x=1;
		var y=0;
		for (var i=0; i<sortlist.length; i++) {
			m = sortlist[i][1];
			n = sortlist[i][2];
			x5663posx[m][n] = Math.round((x+margin)*spacex+x5663screenpadding);
			x5663posy[m][n] = Math.round((y+margin)*spacey+x5663screenpadding);
			x++;
			if (x>=maxx) { x=0; y++ }
		}
	}

	function x5663createtemplate()
	// วาด image ลงใน template
	// โดยวาดห่างๆ แต่ละชิ้นได้พื้นที่ 3*3 เท่าจากปกติ
	{
		// clear template
		var ctx2 = ebid("template1").getContext("2d");
		var ctx3 = ebid("template2").getContext("2d");
		ctx2.setTransform(1,0,0,1,0,0);
		ctx3.setTransform(1,0,0,1,0,0);
		ctx2.clearRect(0,0,ctx2.canvas.width,ctx2.canvas.height);
		ctx3.clearRect(0,0,ctx3.canvas.width,ctx3.canvas.height);

		// กำหนดขนาดเส้น สีเส้น สีเงาแดง
		ctx2.lineWidth = 0.5; ctx2.strokeStyle = "gray";
		ctx3.strokeStyle = "red"; ctx3.fillStyle = "rgba(255,0,0,0.3)";

		// วน loop วาดทีละชิ้น จนครบทุกชิ้น
		var img1 = ebid("img1")
		var w = x5663piecesize[0];
		var h = x5663piecesize[1];
		for (var m=0; m<x5663cutcount[0]; m++) {
		for (var n=0; n<x5663cutcount[1]; n++) {
			ctx2.beginPath();
			ctx3.beginPath();
			x5663jigsawborder(ctx2,ctx3,img1,m,n);
			ctx2.closePath();
			ctx3.closePath();

			// เติมรูปให้กับ template1
			ctx2.save();
			ctx2.clip();
			ctx2.drawImage(img1,0,0,x5663playsize[0],x5663playsize[1]);
			ctx2.stroke();
			ctx2.restore();

			// เติมพื้นสีแดงให้ template2
			ctx3.fill();
		} }
	}

	function x5663jigsawborder(ctx2,ctx3,img1,m,n)
	// วาดชิ้นที่ [m][n] ลงไปบน template
	// พื้นที่ที่จะวาด มีขาด 3*3 ของ piecesize
	// วาดให้ลงช่องกลางของ 3*3
	// ช่องรอบๆ มีไว้สำหรับเผื่อล้นออกมาจากช่องกลาง (ซึ่งมันจะล้นแน่ๆอยู่แล้ว)
	// จุดมุมบนซ้ายของ 3*3 คือ piecewidth*(m*3),pieceheight*(n*3)
	// จุดมุมบนซ้ายของช่องกลางคือ piecewidth*(m*3+1),pieceheight*(n*3+1)
	{
		var piecewidth = x5663piecesize[0];
		var pieceheight = x5663piecesize[1];

		// เตรียม function ย่อย p(dm,dn)
		// function นี้จะอ่านค่าตำแหน่งจุดมุมบนซ้ายของ jigsaw ชิ้นที่อยู่ข้างๆ ชิ้น [m][n]
		// แล้ว return ตำแหน่งขอจุดในรูปแบบ [x,y]
		//	parameter dm,dn เป็นตัวบอกว่าจะเอาชิ้นข้างไหน (ชิ้นบน,ชิ้นล่าง,ชิ้นซ้าย,ชิ้นขวา,หรือชิ้นตัวมันเอง)
		//	ref หมายถึงจุดมุมของชิ้น ก่อนที่จะ random ให้โย้ไปเย้มา
		//	ถ้า m+dm หรือ n+dn เกินช่วงที่มีข้อมูล จะขยับให้กลับมาอยู่ในช่วง
		var maxm = x5663cutcount[0];
		var maxn = x5663cutcount[1];
		var p = function(dm,dn) {
			var mm = m+dm;
			var nn = n+dn;
			if (mm<0) { mm=0 } if (mm>maxm) { mm=maxm }
			if (nn<0) { nn=0 } if (nn>maxn) { nn=maxn }
			var x0 = mm*piecewidth;
			var y0 = nn*pieceheight;
			return [x0 + x5663diffx[mm][nn],y0 + x5663diffy[mm][nn]]; 
		}

		// function ย่อย สำหรับช่วย plot
		// var ctx;
		vmoveto = function(p1) { ctx2.moveTo(p1[0],p1[1]); ctx3.moveTo(p1[0],p1[1]); }
		vlineto = function(p1) { ctx2.lineTo(p1[0],p1[1]);  ctx3.lineTo(p1[0],p1[1]);  }

		var pintop = x5663pinx[m][n];
		var pinright = x5663piny[m+1][n];
		var pinbottom = x5663pinx[m][n+1];
		var pinleft = x5663piny[m][n];

		// วาดกรอบ jigsaw
		var area33x = piecewidth*(m*2+1);
		var area33y = pieceheight*(n*2+1);
		ctx2.setTransform(1,0,0,1,area33x,area33y);
		ctx3.setTransform(1,0,0,1,area33x,area33y);
		vmoveto(p(0,0));
		if (n==0) { vlineto(p(1,0)); } else { x5663jigsawcurve(ctx2,ctx3,p(-1,0),p(0,0),p(1,0),p(2,0),p(0,pintop),p(1,pintop)); }
		if (m==maxm-1) { vlineto(p(1,1)); } else { x5663jigsawcurve(ctx2,ctx3,p(1,-1),p(1,0),p(1,1),p(1,2),p(1-pinright,0),p(1-pinright,1)); }
		if (n==maxn-1) { vlineto(p(0,1)); } else { x5663jigsawcurve(ctx2,ctx3,p(2,1),p(1,1),p(0,1),p(-1,1),p(1,1+pinbottom),p(0,1+pinbottom)); }
		if (m==0) { ctx2.closePath(); ctx3.closePath(); } else { x5663jigsawcurve(ctx2,ctx3,p(0,2),p(0,1),p(0,0),p(0,-1),p(-pinleft,1),p(-pinleft,0)); }
	}

	function x5663jigsawcurve(ctx2,ctx3,aa,a,b,bb,d,c)
	{
		var k1 = 0.10; // ความยาวของเส้นตรงก่อนเข้า s curve
		var k2 = 0.80; // ระยะห่างของจุดคุมความโค้ง #1 ของ s curve ค่า k2 มากขึ้นคอจะแคบลง
		var k3 = 0.30; // ความสูงของหัว
		var k4 = 0.40; // ความกว้างของหัว

		// function ย่อย สำหรับช่วย plot
		vcurveto = function(p1,p2,p3) { 
			if (p3) { ctx2.bezierCurveTo(p1[0],p1[1],p2[0],p2[1],p3[0],p3[1]); ctx3.bezierCurveTo(p1[0],p1[1],p2[0],p2[1],p3[0],p3[1]); } 
			else { ctx2.quadraticCurveTo(p1[0],p1[1],p2[0],p2[1]); ctx3.quadraticCurveTo(p1[0],p1[1],p2[0],p2[1]); }
		}

		// function ย่อย สำหรับช่วยคำนวณ vector 
		vlen = function(a) { return Math.sqrt(a[0]*a[0]+a[1]*a[1]); }
		vunit = function(a) { var lena = vlen(a); return [a[0]/lena,a[1]/lena]; }
		vscale = function(a,k) { return [a[0]*k,a[1]*k]; }
		vadd = function(a,b) { return [a[0]+b[0],a[1]+b[1]]; }
		vsub = function(a,b) { return [a[0]-b[0],a[1]-b[1]]; }
		vmix = function(a,ka,b,kb,c,kc,d,kd) { return [a[0]*ka+b[0]*kb+(c?c[0]*kc:0)+(d?d[0]*kd:0),a[1]*ka+b[1]*kb+(c?c[1]*kc:0)+(d?d[1]*kd:0)]; }

		var lenab = vlen(vsub(a,b));
		var u1 = vunit(vsub(b,aa));
		var u2 = vunit(vsub(a,bb));

		var p1 = vmix(a,1,u1,k1*lenab);
		var p2 = vmix(a,1,u1,k2*lenab);
		var p6 = vmix(b,1,u2,k2*lenab);
		var p7 = vmix(b,1,u2,k1*lenab);
		var p4 = vmix(a,0.5*(1-k3),b,0.5*(1-k3),d,0.5*k3,c,0.5*k3);
		var p3 = vmix(p4,1,b,-k4,a,k4);
		var p5 = vmix(p4,1,a,-k4,b,k4);

		vcurveto(p2,p3,p4);
		vcurveto(p5,p6,b);
		vlineto(b);
	}

	function x5663redraw()
	{
		// จะวาดทีละชิ้น โดย copy จาก template มา paste บน canvas หลัก
		// แต่ละชิ้นใน template จะกินที่ 3 เท่า
		// ถ้าแบ่งพื้นที่เป็น 3*3 ช่อง
		// ภาพของแต่ละชิ้นจะอยู่ที่ช่องกลาง และเผื่อรอบข้างอีก 1 ช่องรอบตัว
		// การวาด จะวางให้มุมบนซ้ายของช่องกลาง ตรงกับ posx,posy

		var cv = ebid("canvas1");
		var template1 = ebid("template1");
		var template2 = ebid("template2");

		var ctx = cv.getContext("2d");
		ctx.setTransform(1,0,0,1,0,0);
		ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
		x5663drawmenubutton(ctx);

		var w = x5663piecesize[0];
		var h = x5663piecesize[1];

		// ลำดับการวาด จะไล่ตามลำดับใน x5663redraworder
		for (var i=0; i<x5663redraworder.length; i++) {
			var m = x5663redraworder[i][0];
			var n = x5663redraworder[i][1];
			var posx = x5663posx[m][n];
			var posy = x5663posy[m][n];
			ctx.drawImage(template1,m*w*3,n*h*3,w*3,h*3,posx-w,posy-h,w*3,h*3);
			if (x5663showselect && x5663selected[m][n]) { ctx.drawImage(template2,m*w*3,n*h*3,w*3,h*3,posx-w,posy-h,w*3,h*3); }
		}
	}

	function x5663drawmenubutton(ctx)
	// วาดรูป "สามขีด" อยู่ใน round corner box ที่มุมบนซ้ายของ canvas
	{
		ctx.beginPath();
		var mb = x5663menubutton;
		var deg = Math.PI/2;
		var rr = 6;
		ctx.arc(mb[0]+rr,mb[1]+rr,rr,deg*2,deg*3);
		ctx.arc(mb[0]+mb[2]-rr,mb[1]+rr,rr,deg*3,deg*4);
		ctx.arc(mb[0]+mb[2]-rr,mb[1]+mb[3]-rr,rr,deg*0,deg*1);
		ctx.arc(mb[0]+rr,mb[1]+mb[3]-rr,rr,deg*1,deg*2);
		ctx.fillStyle = "pink";
		ctx.fill();

		ctx.fillStyle = "black";
		var a,b,c; a=8; b=3; c=2;
		for (var i=0; i<3; i++) {
			ctx.fillRect(mb[0]+rr,mb[1]+a+b*i+c*i,mb[2]-rr*2,b);
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ภาค 4 : โปรแกรมส่วนตอบสนองต่อ mouse

	// ตัวแปรเก็บสถานะของระบบ mouse
	x5663mousedown = 0; // บอกว่าตอนนี้ กด mouse อยู่หรือไม่
	x5663mousebeginpos = [0,0]; // จำตำแหน่งที่กด mouse
	x5663mousemode = "coming soon"; // เก็บสถานะ
		// สถานะที่ใช้งานหลักๆ มีสองสถานะคือ
		// move : กรณีจับแล้วลาก
		// find : กรณีตีกรอบ เพื่อ select แบบล้อมกรอบ
		// จะเริ่มเข้าสถานะใดสถานะหนึ่งข้านต้นเมื่อกด mouse

	// โปรแกรมหลักของภาคการตอบสนองเมาส์
	// เมื่อมีการ กดเมาส์ ลากเมาส์ ปล่อยเมาส์
	// onevent ที่ตั้งไว้ จะกระโดดมาทำที่นี่
	function x5663onmouse(event)
	{
		event.preventDefault(); // ป้องกันการทำงานปกติของ mouse
		event.stopPropagation(); // และป้องกันการเกิด event ซ้ำซ้อน

		// คำนวณตำแหน่ง x,y ของ mouse
		// ตำแหน่งของ x,y ที่ได้จาก event จำเป็นตำแหน่งเทียบกับจอ
		// จะต้องเปลี่ยนเป็นตำแหน่งเทียบกับ canvas ก่อน
		// โดยการเอาตำแหน่งของ canvas ที่เทียบกับจอ ไปลบออก
		var cv = ebid("canvas1");
		var cvcorner = x2147elementcorner(cv);
		var mousex = event.clientX - cvcorner[0];
		var mousey = event.clientY - cvcorner[1];

		// ตรวจสอบว่าเป็นกรณีไหนใน 4 กรณี
		// กรณี 1 กด mouse
		// กรณี 2 ปล่อย mouse
		// กรณี 3 ลาก (ขณะที่กด)
		// กรณี 4 ลากขณะที่ไม่ได้กด
		var etype = event.type;

		// กรณี กด mouse
		if (etype == "mousedown") {
			// เก็บสถานะ
			// เก็บสถานะ mousedown เป็น 1 เพื่อใช้ดูตอน mousemove ว่าลากแบบกด หรือลากแบบไม่กด
			// และเก็บตำแหน่งที่กดไว้ด้วย
			x5663mousedown = 1;
			x5663mousebeginpos = [mousex,mousey]; // เก็บตำแหน่งที่กดเอาไว้

			// ตรวจสอบการกดว่ากดโดนอะไรบ้าง
			// ความหวังที่ 1 ถ้ากดสิ่งที่ select ไว้แล้ว : เตรียมจับทั้งกลุ่มลากไปพร้อมกัน
			// ความหวังที่ 2 กดชิ้นใหม่ : เตียมจับชิ้นใหม่ลาก
			// ความหวังที่ 3 กดโดน menu : แสดง popup menu
			// หมดหวัง กดไม่โดนอะไรเลย กดลงไปบนความว่างเปล่า : เตรียมกรอบเส้นแดง เพื่อล้อมคอกจะ select หมู่

			// ตรวจความหวังที่ 1 กดสิ่งที่ select ไว้กอนหน้านี้
			// ใช้ x5663getselectlist เอารายชื่อชิ้นที่ selct อยู่ออกมา
			// แล้วตรวจรายชื่อด้วย x5663findsinglepiece
			var currentselected = x5663getselectlist(); // list รายการชิ้นที่ถูก select อยู่ในปัจจุบัน
			var foundlist = x5663findsinglepiece(mousex,mousey,currentselected); // ตรวจว่ากดชิ้นใน list หรือไม่
			if (foundlist.length>0) {
				// กรณีพบว่ากดที่ ชิ้นที่ select อยู่แล้ว
				// ก็ select ต่อไปตามเดิม และเข้า mode move
				x5663mousemode = "move";
			} else {
				// กรณี ความหวังที่ 1 ล้มเหลว
				// ยกเลิกที่ select ไว้เดิมก่อน
				// แล้วตรวจความหวังที่ 2 ตรวจว่า select ชิ้นใหม่ ชิ้นอื่นหรือเปล่า
				x5663selectpiece([ ]);
				var foundlist = x5663findsinglepiece(mousex,mousey,x5663redraworder);
				if (foundlist.length>0) {
					// กรณีพบว่า มีการกดชิ้นใหม่
					//	select มัน
					//	expand รวมชิ้นที่เป็นแก๊งเดียวกับมันไปด้วย
					//	bring to front ทุกชิ้นที่ select
					
					x5663selectpiece(foundlist);
					x5663expandselecttogroup();
					x5663bringselecttofront();
					x5663showselect = 0; // select แบบนี้ ไม่ต้องไฮไลท์เงาแดง
					x5663mousemode = "move"; // เตรียมตัวเข้าสู่การจับลาก
					x5663redraw();
				} else {
					// ตรวจสอบความหวังที่ 3 อาจจะกดเมนูก็ได้
					var mb = x5663menubutton;
					if (mousex>=mb[0] && mousey>=mb[1] && mousex<=mb[0]+mb[2] && mousey<=mb[1]+mb[3]) {
						// ถ้าตรวจพบการกด menu ก็เปิด menu
						x5663showmenu();
					} else {
						// กรณีที่กดไม่โดนอะไรเลย กดลงไปบนพื้นที่ว่างเปล่า
						// กรณีจะเข้าสู่การเปิดกรอบแดง เพื่อเตรียม select แบบ ล้อมกรอบ
						// ยกเลิกสิ่งที่ select อยู่เดิมทั้งหมด (ถ้ามี)
						// เปลี่ยน mode เป็น find เพื่อเข้าสู่การ select แบบล้อมกรอบ
						x5663selectpiece([ ]);
						x5663showselect = 0;
						x5663mousemode = "find";
						x5663redraw();
					}
				}
			}
		}

		// มาดูกรณี ลาก mouse ก่อน
		// การลาก จะมี ลากตอนกด หรือลากตอนไม่ได้กด
		// การดูกดหรือไม่กด ให้ดูจากตัวแปร x5663mousedown
		// ถ้าก่อนหน้านี้ ตรวจพบการกด x5663mousedown จะเป็น 1
		// ถ้าก่อนหน้านี้ ตรวจพบการปล่อย  x5663mousedown จะเป็น 0
		// แยกกรณีย่อย
		//	ลากตอนไม่กด : ไม่ทำอะไร
		//	ลากตอนกด : ทำ
		//		ถ้าก่อนหน้านี้ กดลงบนชิ้น jigsaw จะมาใน mode : move เตียมจับลาก
		//		ถ้าก่อนหน้านี้ กดลงบนที่ว่าง จะมาใน mode : find เตรียม select แบบล้อมกรอบ
		if (etype == "mousemove") {
			if (x5663mousedown) {
				if (x5663mousemode == "move") {
					// กรณีจับลาก ก็สั่ง move แล้ว redraw
					var dx = mousex-x5663mousebeginpos[0];
					var dy = mousey - x5663mousebeginpos[1];
					x5663moveselectpiece(dx,dy);
					x5663mousebeginpos = [mousex,mousey];
					x5663redraw();
				}
				if (x5663mousemode == "find") {
					// กรณีค้นหาแบบล้อมกรอบ
					// ก็วาดกรอบเส้นสีแดง ทับการ redraw
					x5663redraw();
					var x1 = x5663mousebeginpos[0];
					var y1 = x5663mousebeginpos[1];
					var x2 = mousex;
					var y2 = mousey;
					var ctx = ebid("canvas1").getContext("2d");
					ctx.lineWidth = 0.5;
					ctx.strokeStyle = "red";
					ctx.strokeRect(x1,y1,x2-x1,y2-y1);
				}
			}
		}

		// กลับมากรณี ปล่อย mouse
		// ต้องแยกเป็นสองกรณีย่อยทำนองเดียวกับตอนลาก
		// ถ้าอยู่ใน mode : move การปล่อย ก็คือการลากมาวาง
		//		มีแถมนิดนึง คือถ้าวางใกล้ชิ้นที่ต่อกันได้ ให้วิ่งเข้าไปดูดติดกันด้วย
		// ถ้าอยู่ใน mode : find การปล่อย ก็คือ ตีกรอบเสร็จ
		//		ตีกรอบเสร็จก็ค้นหาในกรอบที่ล้อมไว้
		//		ถ้าเจอ ก็ selct มันให้หมด
		if (etype == "mouseup" || etype == "mouseout") {
			x5663mousedown = 0; // ตัวแปรนี้ สำหรับเอาไปใช้ใน mouse move หลังจากนี้ ว่าเป็นการลากแบบไม่ได้กด

			if (x5663mousemode == "move") {
				// จากเดิมที่ลากมา ก็จะสิ้นสุดการลาก ณ ที่นี้
				// แต่ก่อนจะจบ แถมสองอย่าง
				// 1 ตรวจสอบด้วยว่ามีชิ้นที่ต่อกันได้อยู่แถวนี้หรือเปล่า
				// 2 ตรวจสอบว่าทีชิ้นไหนหลุดจอไปบ้างหรือไม่ ถ้ามีให้ดึงกลับมา
				if (x5663checkjoinable()) { // ตรวจชิ้นที่ต่อกันได้
					// ถ้ามีและโดดไปดูดติดแล้ว จะ
					// epand ใหม่
					// bring to front ใหม่
					x5663expandselecttogroup();
					x5663bringselecttofront();
				}
				x5663movebackfromoutscreen(); // ตรวจตกจอ
				x5663redraw();
			}
			if (x5663mousemode == "find") {
				// จากเดิมที่อยู่ในสถานะ ค้นหาแบบตีกรอบล้อมคอก
				// เมื่อปล่อยมือ ก็จะเริ่มทำการค้นหา
				var foundlist = x5663findmultipiece(x5663mousebeginpos[0],x5663mousebeginpos[1],mousex,mousey,x5663redraworder);
				if (foundlist.length>0) {
					// ถ้าเจอ ก็ select ทุกตัวที่เจอ
					// expand
					// bring to front
					x5663selectpiece(foundlist);
					x5663expandselecttogroup();
					x5663bringselecttofront();
					x5663showselect = 1; // select แบบตีกรอบจะมีเงาแดง
				}
				x5663redraw();
			}
			x5663mousemode = "good bye"; // ที่จริงจะทิ้ง mode ไว้ตามเดิมก็ได้ ไม่มีใครมาดูแล้ว แต่ล้างทิ้งดีกว่า
		}
	}

	function x5663findsinglepiece(posx,posy,piecelist)
		// ค้นหาใน piecelist ว่ามีชิ้นไหนอยู่ที่ตำแหน่ง posx,posy แล้วจะเจอชิ้นไหน
		// piecelist จะมีโครงสร้าง [[m1,n1],[m2,n2],[m3,n3],...] ซึ่งเป็นโครงสร้างเดียวกับ x5663redraworder
		// ถ้าเจอ จะหยุดแค่ตัวแรก และ return [[mfound,nfound]]
		// ถ้าไม่เจอจะ return เป็น array ว่างๆ
	{
		var found = [ ];
		var maxi = piecelist.length;
		var w = x5663piecesize[0];
		var h = x5663piecesize[1];
		for (var i=maxi-1; i>=0; i--) {
			var m = piecelist[i][0];
			var n = piecelist[i][1];
			var xa,xb,xc,xd, ya,yb,yc,yd;
			x0 = x5663posx[m][n];
			y0 = x5663posy[m][n];
			pa = [x0+x5663diffx[m][n],		y0+x5663diffy[m][n]];
			pb = [x0+w+x5663diffx[m+1][n],	y0+x5663diffy[m+1][n]];
			pc = [x0+w+x5663diffx[m+1][n+1],	y0+h+x5663diffy[m+1][n+1]];
			pd = [x0+x5663diffx[m][n+1],		y0+h+x5663diffy[m][n+1]];
			var ok = x5657pointinabcd([posx,posy],pa,pb,pc,pd);
			if (ok) { found = [[piecelist[i][0],piecelist[i][1]]]; break; }
		}
		return found;
	}

	function x5663findmultipiece(x1,y1,x2,y2,piecelist)
	// ค้นหาใน piecelist ว่ามีชิ้นที่อยู่ในกรอบ x1y1 ... x2y2 บ้าง
	// ค้นโดย
	//	1 ตรวจว่า มีจุดมุมใดมุมหนึ่งใน 4 มุม ที่อยู่ใน area
	//	2 ครวจว่า มีด้านใดด้านหนึ่ง ลากพาดข้าม area
	{
		piecelist = x5663redraworder;
		var found1 = [ ]; // เก็บตำแหน่ง ใน piecelist
		var found2 = [ ]; // เก็บ [m,n]
		var minx = Math.min(x1,x2);
		var miny = Math.min(y1,y2);
		var maxx = Math.max(x1,x2);
		var maxy = Math.max(y1,y2);
		var w = x5663piecesize[0];
		var h = x5663piecesize[1];

		for (var i=0; i<piecelist.length; i++) {
			var m = piecelist[i][0];
			var n = piecelist[i][1];
			var addx = function(dm,dn) { return x5663posx[m][n] + w*dm + x5663diffx[m+dm][n+dn]; }
			var addy = function(dm,dn) { return x5663posy[m][n] + h*dn + x5663diffx[m+dm][n+dn]; }
			var xxxx = [addx(0,0),addx(0,1),addx(1,1),addx(1,0),addx(0,0)]; // จุดที่ 5  คือจุดแรก ที่ใส่ลงไปอีกครั้ง ใช้สำหรับตอนตรวจเส้นขอบด้านที่ 4
			var yyyy = [addy(0,0),addy(0,1),addy(1,1),addy(1,0),addy(0,0)];
			var inarea = 0;
			for (var ii=0; ii<4; ii++) {
				var x1 = xxxx[ii];
				var y1 = yyyy[ii]
				if (x1>minx && x1<maxx && y1>miny && y1<maxy) { inarea = 1; break;  }
				var x2 = xxxx[ii+1];
				var y2 = yyyy[ii+1]
				if ((x1<minx || x2<minx) && (x1>maxx || x2>maxx) && (y1+y2)/2>miny && (y1+y2)/2<maxy) { inarea = 1; break;  }
				if ((x1+x2)/2>minx && (x1+x2)/2<maxx && (y1<miny || y2<miny) && (y1>maxy || y2>maxy)) { inarea = 1; break;  }
			}
			if (inarea) { found2.push([m,n]); }
		}
		return found2;
	}

	function x5663selectpiece(plist)
	// ยกเลิกการ select เดิม
	// แล้ว select ใหม่ โดยจะ select ทุกชิ้นที่ตรงกับใน plist
	{
		var alist = x5663redraworder;
		for (var i=0; i<alist.length; i++) {
			var m = alist[i][0];
			var n = alist[i][1];
			x5663selected[m][n] = 0;
		}
		if (plist.length>0) {
			for (var i=0; i<plist.length; i++) {
				var m = plist[i][0];
				var n = plist[i][1];
				x5663selected[m][n] = 1;
			}
		}
	}

	function x5663getselectlist()
	{
		var selectlist = [ ];
		for (var i=0; i<x5663redraworder.length; i++) {
			var m = x5663redraworder[i][0];
			var n = x5663redraworder[i][1];
			var isselect = x5663selected[m][n];
			if (isselect) { selectlist.push([m,n]); }
		}
		return selectlist;
	}

	function x5663expandselecttogroup()
	// expand การ select โดย
	// ถ้าชิ้นที่ select อยู่มี group จะ select group ของมันด้วย
	{
		var donegroup = [ ]; // สำหรับจำว่า group ไหนทำไปแล้ว จะได้ไม่ต้องทำซ้ำ
		var isdone = function(g1) {
			for (var doneindex=0; doneindex<donegroup.length; doneindex++) {
				if (g1==donegroup[doneindex]) { return true; }
			}
			return false;
		}

		for (var m=0; m<x5663cutcount[0]; m++) {
		for (var n=0; n<x5663cutcount[1]; n++) {
			if (x5663selected[m][n]) {
				var g = x5663joingroup[m][n];
				if (g>-1 && !isdone(g)) {
					for (var m2=0; m2<x5663cutcount[0]; m2++) {
					for (var n2=0; n2<x5663cutcount[1]; n2++) {
						if (x5663joingroup[m2][n2]==g) { x5663selected[m2][n2] = 1; }
					} }
					donegroup.push(g);
				}
			}
		} }
	}

	function x5663bringselecttofront()
	// ย้ายชิ้นที่ select มาไว้ท้ายสุดของ x5663redraworder
	// เพื่อเป็นการ bring to front บนหน้าจอ
	{
		var front = [ ];
		var back = [ ];
		for (var i=0; i<x5663redraworder.length; i++) {
			var m = x5663redraworder[i][0];
			var n = x5663redraworder[i][1];
			if (x5663selected[m][n]) { front.push([m,n]); }
			else { back.push([m,n]); }
		}
		x5663redraworder = back.concat(front);
	}

	function x5663checkjoinable()
	// ตรวจสอบแต่ละชิ้นใน selection
	// ดูว่าชิ้นไหนใกล้ตัวข้างๆมากที่สุด
	// ถ้าค่าใกล้ที่สุด ใกล้พอ
	// จะวิ่งเข้าไปติดโดยอัตโนมัติ
	{
		var w = x5663piecesize[0];
		var h = x5663piecesize[1];
		var maxm = x5663cutcount[0];
		var maxn = x5663cutcount[1];

		// function สำหรับตรวจสอบ m,n กับตัวข้างๆ แล้วเก็บค่าที่ดีที่สุดไว้
		var bestscore = 1000;
		var bestm,bestn,bestdm,bestdn
		var check = function(m,n,dm,dn) {
			var m2 = m+dm;
			var n2 = n+dn;
			var x1 = x5663posx[m][n];
			var y1 = x5663posy[m][n];
			var g1 = x5663joingroup[m][n];
			if (m2>=0 && m2<maxm && n2>=0 && n2<maxn) { // ต้องไม่ตกขอบ
				var g2 = x5663joingroup[m2][n2];
				if (g2==-1 || g2!=g1) { // ถ้าอยู่ในกลุ่มอยู่แล้ว ไม่ต้องเอามาตรวจอีก
					var x2connect = x1+dm*w;
					var y2connect = y1+dn*h;
					var x2real = x5663posx[m+dm][n+dn]
					var y2real = x5663posy[m+dm][n+dn]
					var score = (x2connect-x2real)*(x2connect-x2real)+(y2connect-y2real)*(y2connect-y2real);
					if (score<bestscore) {
						bestscore = score;
						bestm = m;
						bestn = n;
						bestdm = dm;
						bestdn = dn;
					}
				}
			}			
		}

		// วน loop ตรวจทุกชิ้นที่ select ไว้ เพื่อหาค่าที่ดีที่สุด
		for (var m=0; m<x5663cutcount[0]; m++) {
		for (var n=0; n<x5663cutcount[1]; n++) {
			if (x5663selected[m][n] == 1) {
				// ตรวจตัวข้างเคียงทั้ง 4 ด้าน
				// บน ล่าง ซ้าย ขวา
				// ว่าอยู่ใกล้ๆหรือไม่
				check(m,n,-1,0);
				check(m,n,1,0);
				check(m,n,0,-1);
				check(m,n,0,1);
			}
		} }

		// มาดูผลการตรวจ
		// ถ้าชิ้นที่ใกล้ที่สุด ใกล้พอ จะวิ่งเข้าไปหา
		var acceptable = 7.5;
		var needredraw = 0;
		if (bestscore < (acceptable*acceptable)) {
			x2350beep1();
			x5663joinpiece(bestm,bestn,bestm+bestdm,bestn+bestdn);
			needredraw = 1;
		}
		return needredraw;
	}

	function x5663joinpiece(m1,n1,m2,n2)
	// #2 อยู่นิ่ง #1 วิ่งเข้าไปติด
	{
		var w = x5663piecesize[0];
		var h = x5663piecesize[1];

		var dx = x5663posx[m2][n2]+(m1-m2)*w - x5663posx[m1][n1];
		var dy = x5663posy[m2][n2]+(n1-n2)*h - x5663posy[m1][n1];
		var g1 = x5663joingroup[m1][n1];
		if (g1<0) {
			// กรณีไม่มี group วิ่งเข้าไปตัวเดียวก็เสร็จแล้ว
			x5663posx[m1][n1] += dx;
			x5663posy[m1][n1] += dy;
		} else {
			// กรณีตัวที่จะวิ่งมี group จะวิ่งตัวเดียวไม่ได้ ต้องวิ่งทั้ง group
			for (var m=0; m<x5663cutcount[0]; m++) {
			for (var n=0; n<x5663cutcount[1]; n++) {
				if (x5663joingroup[m][n]==g1) {
					x5663posx[m][n] += dx;
					x5663posy[m][n] += dy;
				}
			} }
		}

		// จับรวมเป็นกลุ่มเดียวกัน
		// ทำชื่อกลุ่มให้เหมือนกัน
		var group1 = x5663joingroup[m1][n1]; if (group1<0) { group1 = m1*1000+n1; }
		var group2 = x5663joingroup[m2][n2]; if (group2<0) { group2 = m2*1000+n2; }
		var newgroup = Math.min(group1,group2);
		x5663joingroup[m1][n1] = newgroup;
		x5663joingroup[m2][n2] = newgroup;

		for (var m=0; m<x5663cutcount[0]; m++) {
		for (var n=0; n<x5663cutcount[1]; n++) {
			if (x5663joingroup[m][n] == group1 || x5663joingroup[m][n] == group2) {
				x5663joingroup[m][n] = newgroup;
			}
		} }
	}

	function x5663moveselectpiece(dx,dy)
	{
		for (var m=0; m<x5663cutcount[0]; m++) {
		for (var n=0; n<x5663cutcount[1]; n++) {
			if (x5663selected[m][n] == 1) {
				x5663posx[m][n] += dx;
				x5663posy[m][n] += dy;
			}
		} }
	}

	function x5663movebackfromoutscreen()
	{
		var cv = ebid("canvas1");
		var ww = cv.width;
		var hh = cv.height;
		var w = x5663piecesize[0];
		var h = x5663piecesize[1];
		var counth = x5663cutcount[0];
		var countv = x5663cutcount[1];

		var groupdata = [[ ]];
		for (var gm=0; gm<counth; gm++) {
			groupdata[gm] = [ ];
			for (gn=0; gn<countv; gn++) {
				groupdata[gm][gn] = [0,10000,-10000,10000,-10000,[ ],[ ]]; // membercount,left,right,top,bottom,memberlist [m][n]
			}
		}
		for (var m=0; m<counth; m++) {
		for (n=0; n<countv; n++) {
			var g = x5663joingroup[m][n];
			if (g < 0) { g = m*1000+n; }
			var gm = Math.floor(g/1000);
			var gn = g % 1000;
			var d = groupdata[gm][gn];
			d[0]++;
			d[1] = Math.min(d[1],x5663posx[m][n]);
			d[2] = Math.max(d[2],x5663posx[m][n]);
			d[3] = Math.min(d[3],x5663posy[m][n]);
			d[4] = Math.max(d[4],x5663posy[m][n]);
			d[5].push(m);
			d[6].push(n)
		} }

		var leftborder = -w*0.1;
		var rightborder = ww-w*0.9;
		var topborder = -h*0.1;
		var bottomborder = hh-h*0.9;
		for (var gm=0; gm<counth; gm++) {
		for (gn=0; gn<countv; gn++) {
			var d = groupdata[gm][gn];
			if (d[0]>0) { // d[0] คือ membercount
				var dx = 0;
				var dy = 0;
				if (d[1]>rightborder) { dx = rightborder-d[1]; } 
				if (d[2]<leftborder) { dx = leftborder-d[2]; }
				if (d[3]>bottomborder) { dy = bottomborder-d[3]; } 
				if (d[4]<topborder) { dy = topborder-d[4]; }
				if (dx!=0 || dy!= 0) {
					for (var i=0; i<d[0]; i++) {
						m = d[5][i];
						n = d[6][i];
						x5663posx[m][n] += dx;
						x5663posy[m][n] += dy;
					}
				}
			}
		} }
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ภาค 5 : function ที่ copy มาจาก jsx อื่นๆ
	// x2147elementcorner(htmlelement) : ใช้สำหรับคำนวณตำแหน่งของ mouse ดูรายละเอียดใน jsx2147.html
	// x5657pointinabcd(p,a,b,c,d) : ใช้ตรวจว่าจุด p เป็นจุดที่อยู่ในสี่เหลี่ยม abcd หรือไม่ ดูรายละเอียดใน jsx5657.html
	// x2350beep1() : ส่งเสียง "ปุก" ดูรายละเอียดเรื่องเสียงใน jsx2350.html

	function x2147elementcorner(htmlelement)
	// คำนวณคำแหน่ง มุมบนซ้ายของ htmlelement ว่าเป็น pixcell ที่เท่าไรของจอ
	// เพื่อจะนำไปคำนวณตำแหน่งที่กด mouse
	// 1 : ตำแหน่งที่จะ return เป็นจุดมุมบนซ้ายของ padding ของ htmlelement
	// 2 : ค่าที่ return จะอยู่ในรูปแบบของ vector [X,Y]
	// 3 : ในการคำนวณจะตั้งสมมุติฐานว่า parent element ตัวนอกสุด ไม่มี margin (ถ้ามีต้องไปบวกเพิ่มเอง)
	{
		var sumx = 0;
		var sumy = 0;
		var e;

		// วน loop ที่ 1 สะสม ระยะจาก pdding ถึง padding ของ offsetparent
		e = htmlelement;
		while (e) {
			sumx += e.clientLeft+e.offsetLeft;
			sumy += e.clientTop+e.offsetTop;
			e = e.offsetParent;
		}

		// วน loop ที่ 2 ถ้ามีการ scroll เอาระยะที่ scroll ไปมาลบออก
		// ตรวจสอบทุก parrent ทั้งที่เป็น offsetparent และ ไม่ offsetparent
		e = htmlelement;
		while (e) {
			sumx -= e.scrollLeft;
			sumy -= e.scrollTop;
			e = e.parentElement;
		}

		// return ค่าเป็น vector
		return [sumx,sumy];
	}

	// สำหรับตรวจว่า จุด p อยู่ในสี่เหลี่ยม abcd หรือไม่
	// โดยการดูพื้นที่
	// จาก https://www.geeksforgeeks.org/check-whether-a-given-point-lies-inside-a-triangle-or-not/
	function x5657pointinabcd(p,a,b,c,d) {
		var area = function(p1,p2,p3) { return Math.abs((p2[0]-p1[0])*(p3[1]-p1[1]) - (p2[1]-p1[1])*(p3[0]-p1[0]))/2; }
		return (area(p,a,b)+area(p,b,c)+area(p,c,d)+area(p,d,a)) < 1.01*(area(a,b,c)+area(a,c,d));
	}

	function x2350beep1()
	{
		var beepdata = "data:audio/wav;base64,"+
			"UklGRl4RAABXQVZFZm10IBAAAAABAAEAIlYAAESsAAACABAAZGF0YToRAAC7Fmo/jl9cdf9/mH8k"+
			"dVZiZEnOLCkP4vIZ2nXGEblysoeyurgFxBPTYORc9owHphamIt8q/S4KL14rlSR/GwcRHgas+3ny"+
			"JOsY5ojjceOe5bDpLO+C9SD8egIUCI8MqA8/EVYRDRCbDUwKdgZzApf+Lvtx+Ir2jfV69T32tPez"+
			"+QX8dP7NAOECjAS3BVQGZAbwBQ8F2wNzAvkAjf9J/kP9jPwr/B/8Y/zq/KT9fv5j/0EABgGmARYC"+
			"UgJbAjIC4QFxAe4AYwDc/2T/Av+9/pj+kv6q/tv+Hv9u/8L/FABdAJkAwwDaAN4A0ACzAIoAWgAn"+
			"APX/yf+k/4r/fP95/4H/k/+s/8n/6P8GACEANwBHAFAAUgBNAEIANAAiAA8A/f/s/9//1f/Q/87/"+
			"0f/X/+D/6//2/wIADAAUABoAHQAeABwAGQATAA0ABgD///n/9P/w/+7/7v/v//H/9P/4//z/AAAE"+
			"AAcACQALAAsACwAJAAcABQACAAAA/v/8//r/+f/5//r/+v/8//3///8AAAEAAwADAAQABAAEAAMA"+
			"AwACAAEAAAD///7//v/+//7//v/+//7//////wAAAQABAAEAAQACAAEAAQABAAEAAAAAAAAA////"+
			"//////////////8AAAAAAAAAAAAAAAABAAEAAQ"+
			"A".repeat(38+68*76+11)+
			"=";
		var beepobject = new Audio(beepdata);
		beepobject.volume = 0.2;
		beepobject.play();
	}

// </script>
// <script type='text/javascript'> try { sessionStorage.sourcecode = document.body.innerHTML; } catch (err) { } document.body.innerHTML = ""; x5663main(); </script>





Tag : HTML, JavaScript









ประวัติการแก้ไข
2020-10-29 07:34:43
2020-10-29 07:41:44
2020-10-29 07:48:53
2020-10-29 07:49:41
2020-10-29 07:52:28
2020-10-29 08:17:32
Move To Hilight (Stock) 
Send To Friend.Bookmark.
Date : 2020-10-29 07:31:07 By : r7c4s9 View : 1432 Reply : 1
 

 

No. 1



โพสกระทู้ ( 1,458 )
บทความ ( 0 )



สถานะออฟไลน์
Twitter Facebook Blogger

ขอบคุณสำหรับผลงานสร้างสรรค์ครับ






แสดงความคิดเห็นโดยอ้างถึง ความคิดเห็นนี้
Date : 2020-10-29 10:12:55 By : PhrayaDev
 

   

ค้นหาข้อมูล


   
 

แสดงความคิดเห็น
Re : ตัวอย่างการใช้ javascript เขียนเกมต่อจิ๊กซอว์ ที่สามารถดึงรูปจาก internet มาเล่นได้
 
 
รายละเอียด
 
ตัวหนา ตัวเอียง ตัวขีดเส้นใต้ ตัวมีขีดกลาง| ตัวเรืองแสง ตัวมีเงา ตัวอักษรวิ่ง| จัดย่อหน้าอิสระ จัดย่อหน้าชิดซ้าย จัดย่อหน้ากึ่งกลาง จัดย่อหน้าชิดขวา| เส้นขวาง| ขนาดตัวอักษร แบบตัวอักษร
ใส่แฟลช ใส่รูป ใส่ไฮเปอร์ลิ้งค์ ใส่อีเมล์ ใส่ลิ้งค์ FTP| ใส่แถวของตาราง ใส่คอลัมน์ตาราง| ตัวยก ตัวห้อย ตัวพิมพ์ดีด| ใส่โค้ด ใส่การอ้างถึงคำพูด| ใส่ลีสต์
smiley for :lol: smiley for :ken: smiley for :D smiley for :) smiley for ;) smiley for :eek: smiley for :geek: smiley for :roll: smiley for :erm: smiley for :cool: smiley for :blank: smiley for :idea: smiley for :ehh: smiley for :aargh: smiley for :evil:
Insert PHP Code
Insert ASP Code
Insert VB.NET Code Insert C#.NET Code Insert JavaScript Code Insert C#.NET Code
Insert Java Code
Insert Android Code
Insert Objective-C Code
Insert XML Code
Insert SQL Code
Insert Code
เพื่อความเรียบร้อยของข้อความ ควรจัดรูปแบบให้พอดีกับขนาดของหน้าจอ เพื่อง่ายต่อการอ่านและสบายตา และตรวจสอบภาษาไทยให้ถูกต้อง

อัพโหลดแทรกรูปภาพ

Notice

เพื่อความปลอดภัยของเว็บบอร์ด ไม่อนุญาติให้แทรก แท็ก [img]....[/img] โดยการอัพโหลดไฟล์รูปจากที่อื่น เช่นเว็บไซต์ ฟรีอัพโหลดต่าง ๆ
อัพโหลดแทรกรูปภาพ ให้ใช้บริการอัพโหลดไฟล์ของไทยครีเอท และตัดรูปภาพให้พอดีกับสกรีน เพื่อความโหลดเร็วและไฟล์ไม่ถูกลบทิ้ง

   
  เพื่อความปลอดภัยและการตรวจสอบ กระทู้ที่แทรกไฟล์อัพโหลดไฟล์จากที่อื่น อาจจะถูกลบทิ้ง
 
โดย
อีเมล์
บวกค่าให้ถูก
<= ตัวเลขฮินดูอารบิก เช่น 123 (หรือล็อกอินเข้าระบบสมาชิกเพื่อไม่ต้องกรอก)







Exchange: นำเข้าสินค้าจากจีน, Taobao, เฟอร์นิเจอร์, ของพรีเมี่ยม, ร่ม, ปากกา, power bank, แฟลชไดร์ฟ, กระบอกน้ำ

Load balance : Server 05
ThaiCreate.Com Logo
© www.ThaiCreate.Com. 2003-2024 All Rights Reserved.
ไทยครีเอทบริการ จัดทำดูแลแก้ไข Web Application ทุกรูปแบบ (PHP, .Net Application, VB.Net, C#)
[Conditions Privacy Statement] ติดต่อโฆษณา 081-987-6107 อัตราราคา คลิกที่นี่