เอ็นจิ้นหมากรุกจัดเก็บตำแหน่งที่วิเคราะห์ก่อนหน้านี้ทั้งหมดระหว่างการเคลื่อนไหว


17

ฉันเริ่มเล่นกับเอ็นจิ้นหมากรุก ฉันสังเกตเห็นว่าเครื่องมือหมากรุกที่ดีที่สุดสามารถใช้เวลาหลายนาทีในการย้าย ฉันสงสัยว่าทำไม ก่อนการเคลื่อนที่แต่ละครั้งเครื่องยนต์จะตรวจสอบการเคลื่อนไหวทางกฎหมายในอนาคตทั้งหมด อย่างไรก็ตามดูเหมือนว่าจะทำซ้ำแบบฝึกหัดนี้สำหรับการย้ายครั้งต่อไป ระบุว่าการย้ายก่อนหน้านี้รวมอยู่ในแผนผังการเคลื่อนไหวที่ตรวจสอบแล้วนี่ไม่ได้ผลหรือไม่? หรือฉันเข้าใจผิด?

[แก้ไข: ฉันสมมติว่าเหตุผลที่การวิเคราะห์การย้ายไม่ถูกแคชเนื่องจากข้อ จำกัด หน่วยความจำของคอมพิวเตอร์ที่เพิ่งทำให้การรีสตาร์ทการวิเคราะห์เร็วขึ้น]

คำตอบ:


20

เอ็นจิ้นการเขียนโปรแกรมหมากรุกเป็นดินแดนที่มีความซับซ้อนมากดังนั้นผมจะพาคุณไปที่Chess Programming Wikiซึ่งมีข้อมูลที่ดีมากมายในหัวข้อนี้

พื้นหลัง

การคำนวณหมากรุก (และสิ่งอื่น ๆ ที่คล้ายกัน) นั้นเป็นแบบอย่างและคิดว่าเป็น "แผนผังเกม" หรือ " ต้นไม้ตัดสินใจ " ในวงกว้างต้นไม้นี้เป็นกราฟกำกับโดยมีหนึ่งโหนดที่ด้านบน (ตำแหน่งปัจจุบัน) นำไปสู่โหนดสำหรับการย้ายแต่ละครั้งที่เป็นไปได้ซึ่งแต่ละอันนำไปสู่โหนดเพิ่มเติมสำหรับการย้ายครั้งต่อไปที่เป็นไปได้

ในรูปแบบที่ง่ายแรงที่สุดของพวกเขาเครื่องยนต์หมากรุกสร้างทุกตำแหน่งบนต้นไม้นี้ลงไปขีด จำกัด บางอย่างลึก ( "เร่"), การประเมินแต่ละตำแหน่งที่เกิดขึ้นอยู่กับเงื่อนไขที่ซับซ้อนบาง1 จากนั้นก็เล่นท่าที่ดูเหมือนจะนำไปสู่ผลลัพธ์ที่ดีที่สุด ทุกวันนี้เทคนิคที่ซับซ้อนมาก ๆได้รับการพัฒนาขึ้นเพื่อ จำกัด จำนวนตำแหน่งที่เครื่องยนต์ต้องมอง แต่ฉันจะไม่สนใจสิ่งเหล่านั้นเพื่อจุดประสงค์ในการตอบคำถามนี้เพราะพวกเขาไม่เปลี่ยนปัญหาจริงที่ มือ.

คณิตศาสตร์แทนเจนต์

เหตุผลพื้นฐานที่โดยทั่วไปแล้วเครื่องยนต์ใช้เวลาประมาณเท่ากันในการพิจารณาการเคลื่อนไหวแต่ละครั้งคือขนาดของแผนผังการตัดสินใจเพิ่มขึ้นแบบทวีคูณด้วยความลึก ( k)

พิจารณาตำแหน่งเริ่มต้น ด้านบนของต้นไม้ ( k=0) คือหนึ่งโหนด k=1มียี่สิบอันดับแรกเป็นไปได้สำหรับสีขาวมียี่สิบโหนดที่ระดับความลึกดังนั้น จากนั้นแบล็กก็มีท่าเดินยี่สิบแบบให้เลือกสำหรับแต่ละตัวเลือกของไวท์: ดังนั้นที่k=2นั่นก็มี20 * 20 = 400ตำแหน่งที่เป็นไปได้! และมันจะแย่ลงเมื่อผู้เล่นพัฒนาชิ้นงานของพวกเขา!

สำหรับตัวอย่างเช่นสมมติหลอกว่ามีเสมอยี่สิบย้ายเป็นไปได้สำหรับผู้เล่นแต่ละคนในเวลาใดก็ตาม2 คุณสั่งให้คอมพิวเตอร์มองไปข้างหน้าห้าจังหวะสำหรับผู้เล่นแต่ละคน (สิบชั้น) ลองดูขนาดของต้นไม้กำลังดุร้ายในแต่ละระดับ เพื่อความสนุกเราจะดูจำนวนตำแหน่งทั้งหมดในต้นไม้ (จากด้านบนถึงระดับที่กำหนด)

Ply |    Positions   |  Total Tree Size
----------------------------------------
 0  | 1              | 1
 1  | 20             | 21
 2  | 400            | 421
 3  | 8000           | 8421
 4  | 160000         | 168421
 5  | 3200000        | 3368421
 6  | 64000000       | 67368421
 7  | 1280000000     | 1347368421
 8  | 25600000000    | 26947368421
 9  | 512000000000   | 538947368421
10  | 10240000000000 | 10778947368421

ผลของแต่ละระดับเป็นทวีคูณขนาดใหญ่กว่าระดับก่อนหน้าคือว่าขนาดของต้นไม้ทั้งที่ถูกครอบงำโดยระดับล่าง ลองพิจารณาตัวอย่างด้านบน: ระดับสุดท้ายเพียงอย่างเดียวมีโหนดสิบล้านล้านโหนด ต้นไม้ที่เหลือทั้งหมดมีเพียงห้าแสนล้านเท่านั้น สิบชั้นมีประมาณ 95% ของโหนดในต้นไม้ทั้งหมด (อันที่จริงในแต่ละระดับ) ในทางปฏิบัติสิ่งนี้หมายความว่าเวลาค้นหาทั้งหมดใช้เพื่อประเมินการเคลื่อนไหว "ครั้งสุดท้าย"

ตอบ

ดังนั้นสิ่งนี้เกี่ยวข้องกับคำถามของคุณอย่างไร สมมุติว่าคอมพิวเตอร์ตั้งค่าเป็นสิบชั้นตามที่กล่าวไว้ข้างต้นและยิ่งกว่านั้น "จำ" ผลการประเมินได้ มันคำนวณการย้ายเล่นและจากนั้นคุณทำการย้าย ตอนนี้มีการย้ายไปแล้วสองครั้งดังนั้นมันจะตัดตำแหน่งทั้งหมดจากหน่วยความจำที่เกี่ยวข้องกับการเคลื่อนไหวที่ไม่ได้เกิดขึ้นและถูกทิ้งไว้กับต้นไม้ที่ลงไปอีกแปดการเคลื่อนไหวที่เหลืออยู่ซึ่งคำนวณแล้ว: 26,947,368,421 ตำแหน่ง!

เอาล่ะ! ดังนั้นเราต้องคำนวณสองครั้งสุดท้ายเท่านั้น! การใช้การประมาณ 20 การเคลื่อนไหวในแต่ละระดับความลึกจำนวนการเคลื่อนไหวทั้งหมดที่เราต้องการในการคำนวณที่นี่ยังคงเกินสิบล้านล้าน อันดับที่เราคำนวณนั้นมีเพียงบัญชี 2.5% ของความเป็นไปได้! ดังนั้นแม้โดยการแคชผลลัพธ์ของการเคลื่อนไหวครั้งสุดท้ายสิ่งที่ดีที่สุดที่เราสามารถคาดหวังได้ก็คือการเพิ่มความเร็ว 2.5%! นี่คือเหตุผลว่าทำไมถึงแม้ว่าโปรแกรมของคุณเก็บผลลัพธ์ก่อนหน้านี้โดยปกติคุณจะไม่เห็นการเร่งความเร็วที่สำคัญระหว่างการเคลื่อนไหว (ยกเว้นกรณีที่คอมพิวเตอร์พบคู่บังคับหรือบางสิ่งบางอย่างแน่นอน!)


ข้อจำกัดความรับผิดชอบด้านการทำให้เข้าใจง่าย

มีจำนวนมากของความซับซ้อนที่เกี่ยวข้องในคำถามนี้ซึ่งเป็นเหตุผลที่ผมเชื่อมโยงกับการเขียนโปรแกรมวิกิพีเดียที่ด้านบนมากและพยายามที่เดียวที่จะอธิบายคำตอบในแง่ทางคณิตศาสตร์ในวงกว้าง ในความเป็นจริงโปรแกรมทำโดยทั่วไปส่วนแคชของต้นไม้จากการย้ายไปย้ายและมีสาเหตุอื่นที่ไม่เพียงพอในตัวเอง - เหตุผลบางอย่างง่าย (เช่นสายบางอย่างอาจจะดูออกที่ดีที่จะแปดย้าย แต่จบลงด้วยการกลับมา เพื่อนคู่หูที่ย้ายเก้า!) และหลายคนที่มีความซับซ้อนสูง (โดยทั่วไปเกี่ยวข้องกับวิธีการตัดแต่งกิ่งฉลาด) ดังนั้นคอมพิวเตอร์จะต้องมองไปข้างหน้าต่อไปในความพยายามที่จะหลีกเลี่ยงการตั้งสมมติฐานที่ไม่ดีขึ้นอยู่กับความลึกของการย้ายก่อนหน้านี้


1ฉันจะไม่เข้าร่วมฟังก์ชั่นการเรียนรู้ที่นี่เพราะมันเป็นพื้นที่ที่ซับซ้อนอย่างไม่น่าเชื่อของตัวเอง

2ปัจจัยแขนงเฉลี่ย 20 คือน่าจะต่ำเกินไป


น่าสนใจมากนี่เป็นเหตุผลที่แรมของฉันเกือบจะยุบเมื่อฉันวิเคราะห์อย่างล้ำลึกด้วยเครื่องยนต์ของฉัน
Pablo S. Ocal

ขอบคุณ! น่าสนใจมาก. ฉันพบว่าการสนทนาวิกิเอ็นจิ้นหมากรุกนั้นน่าสนใจ
Dom

3

เอ็นจิ้นหมากรุกทั่วไปจะเก็บตำแหน่งและคะแนนคร่อมแอลฟาเบต้าในตารางการขนย้ายที่สามารถปรึกษาได้ในระหว่างการค้นหาครั้งต่อไป ตารางนี้ไม่ได้รับการปรึกษาโดยตรงเพื่อเลือกการย้ายครั้งต่อไป แต่มันทำให้การค้นหาการเคลื่อนไหวนั้นมีประสิทธิภาพมากขึ้นในสองวิธี

  1. ตำแหน่งจะพบได้หลายครั้งในแผนผังการค้นหาโดยการย้ายหรือเปลี่ยนลำดับของการเคลื่อนไหว เนื่องจากตารางสามารถปรึกษาตำแหน่งดังกล่าวอาจต้องมีการประเมินเพียงไม่กี่ครั้ง (สำหรับความลึกของการค้นหาที่แตกต่างกัน) แทนที่จะเป็นหลายสิบครั้งเมื่อมีการเยี่ยมชมและเยี่ยมชมตำแหน่ง

  2. เทคนิคมาตรฐานสำหรับการค้นหาอัลฟ่า - เบต้าคือการใช้การทำซ้ำที่ลึกลงไปซ้ำ ๆ เพื่อตรวจสอบต้นไม้ที่ความลึกของการค้นหาที่มากขึ้นเรื่อย ๆ จนกว่าจะถึงความลึกของเทอร์มินัล คะแนนการประเมินที่คำนวณในการทำซ้ำก่อนหน้านี้จะใช้ในการสั่งซื้อการเคลื่อนไหวในการทำซ้ำในภายหลัง เป็นที่รู้กันว่าอัลฟ่าเบต้าทำงานได้ดีขึ้น (เช่นตัดทรีของต้นไม้การค้นหามากขึ้น) หากมีการค้นหาการเคลื่อนไหวที่ดีก่อนการเคลื่อนไหวที่ไม่ดี


3

ตัวอย่างหลักฐานหน่วยความจำของเครื่องยนต์:

พิจารณาตำแหน่งที่มีการค้นพบทฤษฎีใหม่ ๆ ในเชิงลึกโดยเฉพาะเกมCaruana vs Topalov ที่เล่นในปีนี้ เมื่อคุณปล่อยให้เครื่องยนต์วิเคราะห์ตำแหน่งหลังจากเคลื่อนที่ 12 เป็นเวลาสั้น ๆ มากขึ้นหรือน้อยลง (พูดประมาณ 10-15 นาที) คุณสามารถตรวจสอบการเคลื่อนไหวที่แนะนำและดูว่า TN ( 13.Re2!) ไม่ปรากฏขึ้นมาในหมู่พวกเขา แนะนำการเคลื่อนไหวด้วยตัวคุณเองย้อนกลับการเคลื่อนไหวแล้วปล่อยให้เครื่องยนต์วิเคราะห์ตำแหน่งเดิมอีกครั้งในเวลาเดียวกันไม่มากก็น้อย น่าแปลกใจหลังจากความคิดบางอย่างตอนนี้เครื่องยนต์จะพิจารณา TN ในการเคลื่อนไหวที่ดีที่สุดและอนุมัติ

แก้ไข: คำตอบเดิม (เก็บไว้ด้านล่าง) ผิดอย่างไรก็ตามมันมีตัวอย่างที่มีประโยชน์ของหน่วยความจำของเครื่องยนต์ซึ่งได้รับการยกมาที่ด้านบน

เท่าที่ฉันรู้พวกเขาทำไม่ได้นั่นคือพวกเขาเริ่มค้นหาต้นไม้เกือบตั้งแต่เริ่มต้นทุกการเคลื่อนไหว

อย่างไรก็ตามพวกเขาจะต้องมีฟังก์ชั่นบางอย่างที่ทำให้ค่าของแต่ละการเคลื่อนไหวเกิดขึ้นจริงและฟังก์ชั่นนี้มีหน่วยความจำระยะสั้นแน่นอน บางตัวอย่างเป็นตำแหน่งที่มีการค้นพบทฤษฎีใหม่ ๆ ในเชิงลึกโดยเฉพาะเกมCaruana vs Topalov ที่เล่นในปีนี้ เมื่อคุณปล่อยให้เครื่องยนต์วิเคราะห์ตำแหน่งหลังจากเคลื่อนที่ 12 เป็นเวลาสั้น ๆ มากขึ้นหรือน้อยลง (พูดประมาณ 10-15 นาที) คุณสามารถตรวจสอบการเคลื่อนไหวที่แนะนำและดูว่า TN ( 13.Re2!) ไม่ปรากฏขึ้นมาในหมู่พวกเขา แนะนำการเคลื่อนไหวด้วยตัวคุณเองย้อนกลับการเคลื่อนไหวแล้วปล่อยให้เครื่องยนต์วิเคราะห์ตำแหน่งเดิมอีกครั้งในเวลาเดียวกันไม่มากก็น้อย น่าแปลกใจหลังจากความคิดบางอย่างตอนนี้เครื่องยนต์จะพิจารณา TN ในการเคลื่อนไหวที่ดีที่สุดและอนุมัติ

ฉันไม่ชำนาญในซอฟต์แวร์หมากรุก แต่เกิดขึ้น นี่สามารถอธิบายได้อย่างน้อยบางส่วนถ้า (ตามที่กล่าวไว้) ฟังก์ชั่นที่ประเมินการเคลื่อนไหวของตำแหน่งมีหน่วยความจำบางส่วน


2
ไม่เครื่องยนต์ไม่เริ่มต้นค้นหาจากต้น อ้างถึงคำตอบของฉัน
SmallChess

ขออภัย แต่ฉันพบว่าคำตอบของคุณอาจทำให้เข้าใจผิดเล็กน้อย
BlueTrin

1
ฉันพยายามทำให้ชัดเจนขึ้น ดังที่ได้กล่าวคำตอบนั้นผิด แต่ตัวอย่างถือเป็นสิ่งที่ดีในการตรวจสอบ (สำหรับเราเรื่องรัก ๆ ใคร่ ๆ มันทำให้เรามีความหวังว่าแม้คอมพิวเตอร์จะแข็งแกร่งกว่ามนุษย์มากบางครั้งสัญชาตญาณประสบการณ์และการทำงานหนักสามารถ "เอาชนะ" เดิม)
Pablo S. Ocal

@pablo ตัวอย่างของคุณใช้ได้ มีหน่วยความจำเพราะครั้งแรกที่คุณเรียกใช้การค้นหาเอ็นจิ้นจะจัดเก็บการประเมินตำแหน่งในตาราง เมื่อคุณค้นหาตำแหน่งเดิมอีกครั้งเครื่องยนต์จะสามารถค้นหาได้เร็วขึ้นมาก ดังนั้นมันจะให้ผลลัพธ์ที่แตกต่าง
SmallChess

การแก้ไขครั้งล่าสุดนั้นสำหรับ @BlueTrin ซึ่งคิดว่ามันทำให้เข้าใจผิด
Pablo S. Ocal

2

Henry Keiter ให้คำตอบทั่วไปแล้วฉันจะให้คำตอบทางเทคนิคเพิ่มเติม มันคือทั้งหมดที่เกี่ยวกับตารางการขนย้ายความลึกของการค้นหาและทางลัด การสนทนาที่นี่เป็นเทคนิคมากขึ้นกว่าคำตอบอื่น ๆ แต่มันจะเป็นประโยชน์กับผู้ที่ต้องการเรียนรู้การเขียนโปรแกรมหมากรุก

มันเป็นความเข้าใจผิดที่พบบ่อยว่าถ้ามีการประเมินตำแหน่งก่อนหน้านี้คะแนนการประเมินสามารถนำกลับมาใช้ใหม่ได้ตราบใดที่มีหน่วยความจำเพียงพอที่จะเก็บการเคลื่อนไหว การเขียนโปรแกรมหมากรุกมีความซับซ้อนมากกว่านั้น แม้จะมีหน่วยความจำที่ไม่มีที่สิ้นสุดคุณยังต้องค้นหาตำแหน่งอีกครั้ง สำหรับการย้ายแต่ละครั้งคะแนนการประเมินจะถูกแนบกับความลึกและขอบเขตของมัน ตัวอย่างเช่นหากเครื่องยนต์จัดเก็บการย้ายด้วยความล้มเหลวสูงรายการในตารางจะมีขอบเขตที่ต่ำกว่า ซึ่งหมายความว่าหากคุณกำลังมองหาตำแหน่งที่คุณยังต้องตรวจสอบขอบเขตว่าคุณสามารถใช้คะแนนการประเมินก่อนหน้านี้ได้หรือไม่

นอกเหนือจากนั้นแต่ละการประเมินมีความลึกที่แนบมากับมัน ในกรอบการทำซ้ำที่ลึกลงไปเมื่อคุณเพิ่มความลึกสำหรับการทำซ้ำแต่ละครั้งคุณจะต้องค้นหาตำแหน่งเหล่านั้นที่คุณได้ทำการค้นหาในการทำซ้ำก่อนหน้านี้แล้ว

คำตอบสั้น ๆ กับคำถามของคุณคือการที่เครื่องยนต์ไม่เก็บทุกตำแหน่งวิเคราะห์ก่อนหน้า (ตราบใดที่หน่วยความจำเพียงพอ) แต่ผลการจัดเก็บไว้ผู้ที่ไม่สามารถนำกลับมาได้อย่างง่ายดายเช่นคุณอาจมีความคิด ในขั้นตอนการเปิดที่มีการทำซ้ำน้อยกว่าผลลัพธ์ที่เก็บไว้เหล่านั้นจะมีประโยชน์มากที่สุดสำหรับการสั่งซื้อแบบเคลื่อนย้ายและฮิวริสติกการลดการย้ายจำนวนหนึ่งโหล ตัวอย่างเช่นใครจะถือว่าการย้ายที่ดีที่สุดจากความลึกสุดท้ายเป็นการเคลื่อนไหวที่ดีที่สุดในความลึกปัจจุบันดังนั้นเราจะเรียงลำดับรายการการย้ายและค้นหาการเคลื่อนไหวที่ดีที่สุดก่อนที่จะย้ายอื่น ๆ หวังว่าเราจะได้รับการตัดยอดต้นที่ล้มเหลวสูง

เราไม่มีหน่วยความจำที่ไม่มีที่สิ้นสุดสำหรับจัดเก็บตำแหน่ง เราต้องกำหนดอัลกอริทึมการแปลงแป้นพิมพ์ อัลกอริทึมคร่ำเครียด Zobrist ให้การแจกแจงแบบหลอกหลอกเรา แต่ไม่ช้าก็เร็วเราก็ยังต้องแทนที่รายการที่มีอยู่บางส่วน


0

เครื่องยนต์แต่ละเครื่องมีรูปแบบการจัดการเวลาของตัวเอง เอ็นจินและ GUI บางตัวช่วยให้คุณกำหนดความเร็วในการเล่นของเครื่องยนต์ เอ็นจิ้นจะคำนวณ / ประเมิน / ขั้นต่ำที่สุดเท่าที่จะสามารถรับข้อ จำกัด ที่กำหนดโดยรูทีนย่อยการจัดการเวลาหรือการตั้งค่าผู้ใช้ หากเครื่องยนต์คิดเป็นเวลานานอาจเป็นเพราะการควบคุมเวลาสำหรับเกมนั้นช้าหรือผู้ใช้ตั้งค่าให้เล่นช้า

ตำแหน่งและการประเมินผลที่เครื่องยนต์ได้คำนวณจะถูกเก็บไว้ในตารางแฮช ผู้ใช้สามารถกำหนดขนาดของแฮชที่มีอยู่ในการตั้งค่าของเครื่องมือ UCI ส่วนใหญ่ เอ็นจิ้นเองใช้ RAM จำนวนหนึ่งและหากคุณตั้งค่าขนาดตารางแฮชสูงเกินไปคอมพิวเตอร์จะเริ่มเก็บแฮชไว้ในฮาร์ดไดรฟ์ในรูปแบบของ RAM เสมือน หน่วยความจำฮาร์ดไดรฟ์เข้าถึงช้ากว่า RAM และโดยปกติคุณจะสามารถได้ยินฮาร์ดไดรฟ์ที่ปั่นป่วน ผู้ใช้จำนวนมากตั้งค่าขนาดตารางแฮชเพื่อให้พอดีกับ RAM ที่มีอยู่

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

เนื่องจากจำนวนแฮชมี จำกัด เครื่องยนต์จึงต้องทำการตัดสินใจเกี่ยวกับข้อมูลที่จะลบออกจากแฮชเมื่อเพิ่มข้อมูลใหม่ เอ็นจิ้นไม่ทราบล่วงหน้าว่าจะเล่นการเคลื่อนไหวใดดังนั้นจึงอาจลบข้อมูลที่จะมีประโยชน์โดยไม่ได้ตั้งใจเนื่องจากมันเพิ่มข้อมูลใหม่

เอนจิ้นโดยทั่วไปไม่ตรวจสอบการเคลื่อนไหวทางกฎหมายทั้งหมดในระดับความลึกที่แน่นอน พวกมันกำจัดกิ่งก้านต้นไม้บางต้นจากการพิจารณาโดยการตัดไปข้างหน้าและข้างหลัง นอกจากนี้หากตำแหน่งใบไม้โหนดมีการจับหรือตรวจสอบที่จะทำเครื่องยนต์จะดำเนินการต่อลงบรรทัดนั้นจนกว่าจะถึงตำแหน่งที่เงียบสงบ (เงียบสงบ) ต้นไม้ที่เกิดขึ้นจริงอาจจะค่อนข้างลึกในบางสถานที่ในขณะที่สายอื่นอาจถูกตัดทอนหลังจากการเคลื่อนไหวเล็กน้อย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.