|
|
|
ตัวอย่างการใช้ javascript เขียนเกมต่อจิ๊กซอว์ ที่สามารถดึงรูปจาก internet มาเล่นได้ |
|
|
|
|
|
|
|
โปรแกรมนี้พิเศษที่สามารถจะเอารูปอะไรก็ได้ จาก internet มา ต่อจิ๊กซอว์ ได้
สำหรับใครที่จะลองเล่นดู
ตอนนี้ยังหา host ลงไม่ได้ เอาใส่ไว้ใน googlesite ชั่วคราวไปก่อน
https://sites.google.com/view/jsxjavascriptexample/jsx/jsx5663
สำหรับเพื่อนๆ น้องๆ ที่อยากศึกษาตัวอย่างการเขียน 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]+"\";'> </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;'> "+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
|
|
|
|
|
Date :
2020-10-29 07:31:07 |
By :
r7c4s9 |
View :
1741 |
Reply :
1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ขอบคุณสำหรับผลงานสร้างสรรค์ครับ
|
|
|
|
|
Date :
2020-10-29 10:12:55 |
By :
PhrayaDev |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Load balance : Server 04
|