ป่าไม้ - ระบบนิเวศจำลอง


19

บันทึก

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

ในปัญหานี้เราจะจำลองฟอเรสต์

สำหรับป่าจำลองนี้เราจะจัดการกับ 3 ด้าน

  • ต้นไม้ที่สามารถเป็นต้นไม้ต้นอ่อนต้นไม้หรือต้นไม้อาวุโส
  • Lumberjacks (เขาตัดต้นไม้เขากินอาหารกลางวันและไปที่ Lava-try)
  • Bears (เขาขยี้คนตัดไม้ที่มีกลิ่นเหมือนแพนเค้ก)

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

รอบเวลา:

การจำลองจะจำลองเป็นเดือน คุณจะต้องทำล่วงหน้าด้วย "เห็บ" "ขีด" แต่ละอันแทนเดือน ทุก 12 "เห็บ" หมายถึงปี ป่าของเราจะเปลี่ยนแปลงและเปลี่ยนแปลงอย่างต่อเนื่อง เราจะบันทึกความคืบหน้าของป่าของเราและวิเคราะห์สิ่งที่เกิดขึ้นกับมัน

ป่าไม้:

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

ป่าของเราจะวางไข่แบบสุ่มตามขนาด ตัวอย่างเช่นหากค่าของคุณเป็น = N คุณจะมี 10 ถึง 10 ฟอเรสต์และ 100 สปอต

  • 10% ของฟอเรสต์จะถือ Lumberjack ใน 10 แต้มสุ่ม (การใช้ 100 สปอตฟอเรสต์ของเรานี่ควรเป็น 10 ไม้)
  • 50% ของป่าจะถือต้นไม้ (ต้นไม้สามารถเป็นหนึ่งใน 3 ชนิดและจะเริ่มต้นเป็นหนึ่งกลางของ "ต้นไม้") ในจุดสุ่ม
  • 2% ของป่าจะถือหมี

วิธีรับขนาดของฟอเรสต์ขึ้นอยู่กับคุณ (อ่านจาก stdin, ไฟล์หรือ hardcode ใน) ฉันอยากจะแนะนำให้รักษา N ชอบ 5 หรือสูงกว่า ป่าเล็ก ๆ ไม่สนุกเท่าไหร่

กิจกรรม:

ในระหว่างการจำลองจะมีกิจกรรม เหตุการณ์ที่เกิดขึ้นขึ้นอยู่กับตรรกะบางอย่างซึ่งฉันจะอธิบายด้านล่าง ฉันจะอธิบายเหตุการณ์ด้านล่างในแต่ละคำอธิบายของ 3 องค์ประกอบของป่าของเรา

เหตุการณ์เป็นไปตามคำสั่งของต้นไม้ก่อนไม้แปรรูปที่สองและสุดท้าย

ต้นไม้:

  • ต้นไม้ทุกเดือนมีโอกาส 10% ที่จะวางไข่ "Sapling" ใหม่ ในพื้นที่โล่งสุ่มที่อยู่ติดกับทรีคุณมีโอกาส 10% ในการสร้าง "ต้นอ่อน"

  • ตัวอย่างเช่นต้นไม้ที่อยู่กลางป่ามี 8 จุดรอบ ๆ หนึ่งในเหล่านี้ (ถ้าว่าง) จะกลายเป็น "ต้นอ่อน"

  • หลังจาก 12 เดือนที่มีอยู่ "Sapling" จะถูกอัพเกรดเป็น "Tree" "Sapling" ไม่สามารถวางไข่ต้นไม้อื่น ๆ ได้จนกว่าจะครบกำหนดเป็น "ต้นไม้"

  • เมื่อ "Sapling" กลายเป็นต้นไม้มันสามารถวางไข่ "Saplings" ใหม่อื่น ๆ ได้

  • เมื่อต้นไม้ "ได้รับรอบ 120 เดือน (10 ปี) มันจะกลายเป็น" ต้นไม้ต้น "

  • Elder Trees มีโอกาส 20% ที่จะวางไข่ "Sapling" ใหม่แทน 10%

  • หากไม่มีจุดที่อยู่ติดกันเปิดไปที่ต้นไม้หรือต้นไม้ใหญ่มันจะไม่วางไข่ต้นไม้ใหม่

Lumberjacks:

คนตัดไม้ตัดต้นไม้พวกเขากระโดดข้ามและชอบกดดอกไม้ป่า

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

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

  • Lumberjacks จะไม่เก็บเกี่ยว "ต้นกล้า"

  • ไม้แปรรูปยังเก็บเกี่ยวต้นไม้ของผู้เฒ่า ต้นไม้ต้นมีค่าไม้ 2 ชิ้น

การติดตามไม้:

ทุก ๆ 12 เดือนปริมาณไม้ที่เก็บเกี่ยวได้ถูกเปรียบเทียบกับจำนวนไม้แปรรูปในป่า

  • หากไม้ที่เก็บรวบรวมมีค่าเท่ากับหรือเกินกว่าจำนวนของ lumberjacks ในป่าจำนวนของ lumberjacks ใหม่ได้รับการว่าจ้างและสุ่มวางไข่ในป่า

  • หาจำนวนไม้แปรรูปที่จะจ้างด้วย: floor(lumber_collected / number_of_lumberjacks)

  • อย่างไรก็ตามถ้าหลังจาก 12 เดือนที่ผ่านไปจำนวนไม้ที่เก็บได้นั้นต่ำกว่าจำนวนของคนตัดไม้แล้วคนตัดไม้จะถูกเก็บไว้เพื่อประหยัดเงินและคนตัดไม้แบบสุ่ม 1 ตัวจะถูกลบออกจากป่า โปรดทราบว่าคุณจะไม่ลดกำลังแรงงาน Lumberjack ของคุณต่ำกว่า 0

หมี:

หมีพเนจรป่าเหมือนคนตัดไม้ อย่างไรก็ตามแทนที่จะมีช่องว่าง 3 ช่อง Bear จะท่องเที่ยวได้ถึง 5 ช่องว่าง

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

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

  • เราจะติดตามสิ่งนี้ว่าเป็นอุบัติเหตุ "ขยำ"

  • โปรดทราบว่าประชากรของคนตัดไม้ไม่สามารถลดลงต่ำกว่า 1 - ดังนั้นหากคนตัดไม้คนสุดท้ายถูกขยำเพียงแค่วางไข่อีกคนหนึ่ง

การติดตามขย้ำ:

  • ในช่วงระยะเวลา 12 เดือนหากมีการเกิดอุบัติเหตุ 0 "Maul" ประชากรหมีจะเพิ่มขึ้น 1 หากมีอุบัติเหตุ "Maul" Lumberjacks จะจ้างสวนสัตว์เพื่อดักจับและนำหมีออกไป ลบหมีสุ่ม 1 ตัว โปรดทราบว่าหากประชากรหมีของคุณถึง 0 หมีแล้วจะไม่มีอุบัติเหตุ "ขยำ" ในปีถัดไปดังนั้นคุณจะวางไข่หมีใหม่ 1 ตัวในปีหน้า

  • หากมีคนตัดไม้เพียง 1 คนในป่าและเขาได้รับการขย้ำเขาจะถูกส่งกลับบ้าน แต่จะมีการจ้างคนใหม่ทันที ประชากรไม้แปรรูปไม่สามารถลดลงต่ำกว่า 1

เวลา:

การจำลองเกิดขึ้นเป็นเวลา 4800 เดือน (400 ปี) หรือจนกว่าจะไม่มีต้นกล้าต้นไม้หรือต้นไม้ต้นโต

เอาท์พุท:

ทุกเดือนคุณจะพิมพ์แผนที่ของฟอเรสต์ - อาจใช้แผนที่ ASCII หรือใช้กราฟิกและสี

บริการเสริม

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

เกณฑ์การให้คะแนน

นี่คือการประกวดความนิยมผู้ชนะมากที่สุดจึงชนะ!

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


เกี่ยวกับ: Note that you will never reduce your Lumberjack labor force below 0ในรายการส่วนคนตัดไม้ 3. อาจเปลี่ยนเป็น 1 เพื่อให้สอดคล้องกับสิ่งที่คุณพูดถึงในส่วนของหมี?
Teun Pronk

จุดดีจะแก้ไขในตอนนี้
James Williams

2
คุณสามารถพูดคุยเรื่องเวลาของการเคลื่อนไหวได้หรือไม่? มันคือ "ต้นไม้ย้าย / ล่วงหน้า" จากนั้น "ย้ายไม้แปรรูป" จากนั้น "หมีย้าย"? นอกจากนี้หมีและต้นไม้สามารถครอบครองพื้นที่เดียวกันได้หรือไม่?
durron597

1
จะเกิดอะไรขึ้นถ้าหมีสองตัวเดินไปที่เดียวกัน แล้วสองขอนไม้ล่ะ? พวกเขาสามารถอยู่ในจุดเดียวกันได้หรือไม่?
Jerry Jeremiah

1
มันเหมือนกับreddit.com/r/dailyprogrammer/comments/27h53e/ ......คุณควรให้เครดิต - อย่างน้อยผู้คนก็สามารถไปที่นั่นเพื่อหาทางออกที่น่าสนใจอื่น ๆ

คำตอบ:


15

Javascript + HTML - ลองใช้

อัปเดตตามคำขอยอดนิยม

ป่าไม้และกราฟ

พฤติกรรมทั่วไป

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

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

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

ปุ่มอนุญาตให้หยุด / เริ่มการจำลองหรือใช้งานเป็นเวลาหนึ่งเดือนหรือหนึ่งปี

การเคลื่อนไหวของผู้อยู่อาศัยในป่าตอนนี้ค่อนข้างมีชีวิตชีวา การลอกคราบและการตัดต้นไม้ก็เกิดขึ้นเช่นกัน

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

ข้างสนามเด็กเล่นชุดกราฟิกที่ปรับขนาดอัตโนมัติจะถูกดึงออกมา:

  • ประชากรหมีและคนตัดไม้
  • จำนวนต้นไม้ทั้งหมดแบ่งออกเป็นต้นกล้าต้นใหญ่และต้นโต

คำอธิบายแผนภูมิยังแสดงปริมาณปัจจุบันของแต่ละรายการด้วย

ความเสถียรของระบบ

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

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

กฎบางประเด็นที่ถกเถียงกัน

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

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

tweaking

เพื่อให้เกิดเสถียรภาพฉันได้เพิ่มพารามิเตอร์ที่ปรับแต่งสองอย่าง:

1) อัตราการเติบโตของคนตัดไม้

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

2) เกณฑ์กำจัดหมี

สัมประสิทธิ์ที่กำหนดเปอร์เซ็นต์ต่ำสุดของการตัดไม้เพื่อส่งหมีไปที่สวนสัตว์ ตั้งค่าเป็น 0 เพื่อกลับไปที่คำจำกัดความดั้งเดิม แต่การกำจัดหมีที่รุนแรงนี้โดยทั่วไปจะ จำกัด ประชากรไว้ที่วัฏจักรการสั่น 0-1 ฉันตั้งค่าเป็น. 15 (เช่นหมีจะถูกลบออกเฉพาะในกรณีที่ 15% หรือมากกว่าของไม้แปรรูปถูกทำลายในปีนี้) สิ่งนี้ช่วยให้ประชากรหมีอยู่ในระดับปานกลางเพียงพอที่จะป้องกันไม่ให้คนใจแคบเช็ดบริเวณที่สะอาด แต่ยังคงอนุญาตให้ตัดส่วนที่ใหญ่ที่สุดของป่า

ในฐานะที่เป็นบันทึกย่อด้านการจำลองไม่เคยหยุดนิ่ง (แม้จะผ่าน 400 ปีที่ผ่านมา) มันสามารถทำได้อย่างง่ายดาย แต่ไม่

รหัส

รหัสมีอยู่ทั้งหมดในหน้า HTML เดียว
จะต้องมีการเข้ารหัส UTF-8เพื่อแสดงสัญลักษณ์ยูนิโค้ดที่เหมาะสมสำหรับหมีและไม้แปรรูป

สำหรับระบบที่มีความบกพร่อง Unicode (เช่น Ubuntu): ค้นหาบรรทัดต่อไปนี้:

    jack   :{ pic: '🙎', color:'#bc0e11' },
    bear   :{ pic: '🐻', color:'#422f1e' }},

และเปลี่ยนรูปสัญลักษณ์สำหรับตัวละครที่ง่ายต่อการแสดงผล ( #, *สิ่งที่)

<!doctype html>
<meta charset=utf-8>
<title>Of jacks and bears</title>
<body onload='init();'>
    <style>
    #log p { margin-top: 0; margin-bottom: 0; }
    </style>
    <div id='main'>

    </div>
    <table>
        <tr>
            <td><canvas id='forest'></canvas></td>
            <td>
                <table>
                    <tr>
                        <td colspan=2>
                            <div>Forest size     <input type='text' size=10 onchange='create_forest(this.value);'>     </div>
                            <div>Simulation tick <input type='text' size= 5 onchange='set_tick(this.value);'     > (ms)</div>
                            <div>
                                <input type='button' value='◾'       onclick='stop();'>
                                <input type='button' value='▸'       onclick='start();'>
                                <input type='button' value='1 month' onclick='start(1);'>
                                <input type='button' value='1 year'  onclick='start(12);'>
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td id='log' colspan=2>
                        </td>
                    </tr>
                    <tr>
                        <td><canvas id='graphs'></canvas></td>
                        <td id='legend'></td>
                    </tr>
                    <tr>
                        <td align='center'>evolution over 60 years</td>
                        <td id='counters'></td>
                    </tr>
                </table>
            </td>
        </tr>
    </table>
<script>
// ==================================================================================================
// Global parameters
// ==================================================================================================

var Prm = {
    // ------------------------------------
    // as defined in the original challenge
    // ------------------------------------

    // forest size
    forest_size: 45, // 2025 cells

    // simulation duration
    duration: 400*12, // 400 years

    // initial populations
    populate: { trees: .5, jacks:.1, bears:.02 },

    // tree ages
    age: { mature:12, elder:120 },

    // tree spawning probabilities
    spawn: { sapling:0, mature:.1, elder:.2 },

    // tree lumber yields
    lumber: { mature:1, elder:2 },

    // walking distances
    distance: { jack:3, bear:5 },

    // ------------------------------------
    // extra tweaks
    // ------------------------------------

    // lumberjacks growth rate
    // (set to 1 in original contest parameters)
    jacks_growth: 1, // .5,

    // minimal fraction of lumberjacks mauled to send a bear to the zoo
    // (set to 0 in original contest parameters)
    mauling_threshold: .15, // 0,

    // ------------------------------------
    // internal helpers
    // ------------------------------------

    // offsets to neighbouring cells
    neighbours: [ 
    {x:-1, y:-1}, {x: 0, y:-1}, {x: 1, y:-1},
    {x:-1, y: 0},               {x: 1, y: 0},
    {x:-1, y: 1}, {x: 0, y: 1}, {x: 1, y: 1}],

    // ------------------------------------
    // goodies
    // ------------------------------------

    // bear and people names
    names: 
    { bear: ["Art", "Ursula", "Arthur", "Barney", "Bernard", "Bernie", "Bjorn", "Orson", "Osborn", "Torben", "Bernadette", "Nita", "Uschi"],
     jack: ["Bob", "Tom", "Jack", "Fred", "Paul", "Abe", "Roy", "Chuck", "Rob", "Alf", "Tim", "Tex", "Mel", "Chris", "Dave", "Elmer", "Ian", "Kyle", "Leroy", "Matt", "Nick", "Olson", "Sam"] },

    // months
    month: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ],

    // ------------------------------------
    // graphics
    // ------------------------------------

    // messages verbosity (set to 2 to be flooded, -1 to have no trace at all)
    verbosity: 1,

     // pixel sizes
     icon_size: 100,
     canvas_f_size: 600,   // forest canvas size
     canvas_g_width : 400, // graphs canvas size
     canvas_g_height: 200,

     // graphical representation
     graph: { 
        soil: { color: '#82641e' },
        sapling:{ radius:.1, color:'#52e311', next:'mature'},
        mature :{ radius:.3, color:'#48b717', next:'elder' },
        elder  :{ radius:.5, color:'#8cb717', next:'elder' },
        jack   :{ pic: '🙎', color:'#2244ff' },
        bear   :{ pic: '🐻', color:'#422f1e' },
        mauling:{ pic: '★', color:'#ff1111' },
        cutting:{ pic: '●', color:'#441111' }},

    // animation tick
    tick:100 // ms
};

// ==================================================================================================
// Utilities
// ==================================================================================================

function int_rand (num)
{
    return Math.floor (Math.random() * num);
}

function shuffle (arr)
{
    for (
        var j, x, i = arr.length;
        i; 
        j = int_rand (i), x = arr[--i], arr[i] = arr[j], arr[j] = x);
}

function pick (arr)
{
    return arr[int_rand(arr.length)];
}

function message (str, level)
{
    level = level || 0;
    if (level <= Prm.verbosity)
    {
        while (Gg.log.childNodes.length > 10) Gg.log.removeChild(Gg.log.childNodes[0]);
        var line = document.createElement ('p');
        line.innerHTML = Prm.month[Forest.date%12]+" "+Math.floor(Forest.date/12)+": "+str;
        Gg.log.appendChild (line);
    }
}

// ==================================================================================================
// Forest
// ==================================================================================================

// --------------------------------------------------------------------------------------------------
// a forest cell
// --------------------------------------------------------------------------------------------------
function cell()
{
    this.contents = [];
}

cell.prototype = {

    add: function (elt)
    {
        this.contents.push (elt);
    },

    remove: function (elt)
    {
        var i = this.contents.indexOf (elt);
        this.contents.splice (i, 1);
    },

    contains: function (type)
    {
        for (var i = 0 ; i != this.contents.length ; i++)
        {
            if (this.contents[i].type == type)
            {
                return this.contents[i];
            }
        }
        return null;
    }
}

// --------------------------------------------------------------------------------------------------
// an entity (tree, jack, bear)
// --------------------------------------------------------------------------------------------------
function entity (x, y, type)
{
    this.age = 0;
    switch (type)
    {
        case "jack": this.name = pick (Prm.names.jack); break;
        case "bear": this.name = pick (Prm.names.bear); break;
        case "tree": this.name = "sapling"; Forest.t.low++; break;
    }

    this.x = this.old_x = x;
    this.y = this.old_y = y;
    this.type = type;
}

entity.prototype = {
    move: function ()
    {
        Forest.remove (this);
        var n = neighbours (this);
        this.x = n[0].x;
        this.y = n[0].y;
        return Forest.add (this);
    }
};

// --------------------------------------------------------------------------------------------------
// a list of entities (trees, jacks, bears)
// --------------------------------------------------------------------------------------------------
function elt_list (type)
{
    this.type = type;
    this.list = [];
}

elt_list.prototype = {
    add: function (x, y)
    {
        if (x === undefined) x = int_rand (Forest.size);
        if (y === undefined) y = int_rand (Forest.size);
        var e = new entity (x, y, this.type);
        Forest.add (e);
        this.list.push (e);
        return e;
    },

    remove: function (elt)
    {
        var i;
        if (elt) // remove a specific element (e.g. a mauled lumberjack)
        {
            i = this.list.indexOf (elt);
        }
        else // pick a random element (e.g. a bear punished for the collective pancake rampage)
        {           
            i = int_rand(this.list.length);
            elt = this.list[i];
        }
        this.list.splice (i, 1);
        Forest.remove (elt);
        if (elt.name == "mature") Forest.t.mid--;
        if (elt.name == "elder" ) Forest.t.old--;
        return elt;
    }
};

// --------------------------------------------------------------------------------------------------
// global forest handling
// --------------------------------------------------------------------------------------------------
function forest (size)
{
    // initial parameters
    this.size = size;
    this.surface = size * size;
    this.date = 0;
    this.mauling = this.lumber = 0;
    this.t = { low:0, mid:0, old:0 };

    // initialize cells
    this.cells = new Array (size);
    for (var i = 0 ; i != size ; i++)
    {
        this.cells[i] = new Array(size);
        for (var j = 0 ; j != size ; j++)
        {
            this.cells[i][j] = new cell;
        }
    }

    // initialize entities lists
    this.trees = new elt_list ("tree");
    this.jacks = new elt_list ("jack");
    this.bears = new elt_list ("bear");
    this.events = [];
}

forest.prototype = {
    populate: function ()
    {
        function fill (num, list)
        {
            for (var i = 0 ; i < num ; i++)
            {
                var coords = pick[i_pick++];
                list.add (coords.x, coords.y);
            }
        }

        // shuffle forest cells
        var pick = new Array (this.surface);
        for (var i = 0 ; i != this.surface ; i++)
        {
            pick[i] = { x:i%this.size, y:Math.floor(i/this.size)};
        }
        shuffle (pick);
        var i_pick = 0;

        // populate the lists
        fill (Prm.populate.jacks * this.surface, this.jacks);
        fill (Prm.populate.bears * this.surface, this.bears);
        fill (Prm.populate.trees * this.surface, this.trees);
        this.trees.list.forEach (function (elt) { elt.age = Prm.age.mature; });
    },

    add: function (elt)
    {
        var cell = this.cells[elt.x][elt.y];
        cell.add (elt);
        return cell;
    },

    remove: function (elt)
    {
        var cell = this.cells[elt.x][elt.y];
        cell.remove (elt);
    },

    evt_mauling: function (jack, bear)
    {
        message (bear.name+" sniffs a delicious scent of pancake, unfortunately for "+jack.name, 1);
        this.jacks.remove (jack);
        this.mauling++;
        Gg.counter.mauling.innerHTML = this.mauling;
        this.register_event ("mauling", jack);
    },

    evt_cutting: function (jack, tree)
    {
        if (tree.name == 'sapling') return; // too young to be chopped down
        message (jack.name+" cuts a "+tree.name+" tree: lumber "+this.lumber+" (+"+Prm.lumber[tree.name]+")", 2);
        this.trees.remove (tree);
        this.lumber += Prm.lumber[tree.name];
        Gg.counter.cutting.innerHTML = this.lumber;
        this.register_event ("cutting", jack);
    },

    register_event: function (type, position)
    {
        this.events.push ({ type:type, x:position.x, y:position.y});
    },

    tick: function()
    {
        this.date++;
        this.events = [];

        // monthly updates
        this.trees.list.forEach (b_tree);
        this.jacks.list.forEach (b_jack);
        this.bears.list.forEach (b_bear);

        // feed graphics
        Gg.graphs.trees.add (this.trees.list.length);
        Gg.graphs.jacks.add (this.jacks.list.length);
        Gg.graphs.bears.add (this.bears.list.length);
        Gg.graphs.sapling.add (this.t.low);
        Gg.graphs.mature .add (this.t.mid);
        Gg.graphs.elder  .add (this.t.old);

        // yearly updates
        if (!(this.date % 12))
        {
            // update jacks
            if (this.jacks.list.length == 0)
            {
                message ("An extra lumberjack is hired after a bear rampage");
                this.jacks.add ();
            }

            if (this.lumber >= this.jacks.list.length)
            {
                var extra_jacks = Math.floor (this.lumber / this.jacks.list.length * Prm.jacks_growth);
                message ("A good lumbering year. Lumberjacks +"+extra_jacks, 1);
                for (var i = 0 ; i != extra_jacks ; i++) this.jacks.add ();
            }
            else if (this.jacks.list.length > 1)
            {
                var fired = this.jacks.remove();
                message (fired.name+" has been chopped", 1);
            }

            // update bears
            if (this.mauling > this.jacks.list.length * Prm.mauling_threshold)
            {
                var bear = this.bears.remove();
                message (bear.name+" will now eat pancakes in a zoo", 1);
            }
            else
            {
                var bear = this.bears.add();
                message (bear.name+" starts a quest for pancakes", 1);
            }

            // reset counters
            this.mauling = this.lumber = 0;
        }
    }

}

function neighbours (elt)
{
    var ofs,x,y;
    var list = [];
    for (ofs in Prm.neighbours)
    {
        var o = Prm.neighbours[ofs];
        x = elt.x + o.x;
        y = elt.y + o.y;
        if (  x < 0 || x >= Forest.size
           || y < 0 || y >= Forest.size) continue;

        list.push ({x:x, y:y});
    }
    shuffle (list);
    return list;
}

// --------------------------------------------------------------------------------------------------
// entities behaviour
// --------------------------------------------------------------------------------------------------
function b_tree (tree)
{
    // update tree age and category
    if      (tree.age == Prm.age.mature) { tree.name = "mature"; Forest.t.low--; Forest.t.mid++; }
    else if (tree.age == Prm.age.elder ) { tree.name = "elder" ; Forest.t.mid--; Forest.t.old++; }
    tree.age++;

    // see if we can spawn something
    if (Math.random() < Prm.spawn[tree.name])
    {
        var n = neighbours (tree);
        for (var i = 0 ; i != n.length ; i++)
        {
            var coords = n[i];
            var cell = Forest.cells[coords.x][coords.y];
            if (cell.contains("tree")) continue;
            Forest.trees.add (coords.x, coords.y);
            break;
        }
    }
}

function b_jack (jack)
{
    jack.old_x = jack.x;
    jack.old_y = jack.y;

    for (var i = 0 ; i != Prm.distance.jack ; i++)
    {
        // move
        var cell = jack.move ();

        // see if we stumbled upon a bear
        var bear = cell.contains ("bear");
        if (bear)
        {
            Forest.evt_mauling (jack, bear);
            break;
        }

        // see if we reached an harvestable tree
        var tree = cell.contains ("tree");
        if (tree)
        {
            Forest.evt_cutting (jack, tree);
            break;
        }
    }
}

function b_bear (bear)
{
    bear.old_x = bear.x;
    bear.old_y = bear.y;

    for (var i = 0 ; i != Prm.distance.bear ; i++)
    {
        var cell = bear.move ();
        var jack = cell.contains ("jack");
        if (jack)
        {
            Forest.evt_mauling (jack, bear);
            break; // one pancake hunt per month is enough
        }
    }
}

// --------------------------------------------------------------------------------------------------
// Graphics
// --------------------------------------------------------------------------------------------------
function init()
{
    function create_counter (desc)
    {
        var counter = document.createElement ('span');
        var item = document.createElement ('p');
        item.innerHTML = desc.name+"&nbsp;";
        item.style.color = desc.color;
        item.appendChild (counter);
        return { item:item, counter:counter };
    }

    // initialize forest canvas
    Gf = { period:20, tick:0 };
    Gf.canvas = document.getElementById ('forest');
    Gf.canvas.width  =
    Gf.canvas.height = Prm.canvas_f_size;
    Gf.ctx = Gf.canvas.getContext ('2d');
    Gf.ctx.textBaseline = 'Top';

    // initialize graphs canvas
    Gg = { counter:[] };
    Gg.canvas = document.getElementById ('graphs');
    Gg.canvas.width  = Prm.canvas_g_width;
    Gg.canvas.height = Prm.canvas_g_height;
    Gg.ctx = Gg.canvas.getContext ('2d');

    // initialize graphs
    Gg.graphs = {
        jacks:   new graphic({ name:"lumberjacks" , color:Prm.graph.jack.color }),
        bears:   new graphic({ name:"bears"       , color:Prm.graph.bear.color, ref:'jacks' }),
        trees:   new graphic({ name:"trees"       , color:'#0F0' }),
        sapling: new graphic({ name:"saplings"    , color:Prm.graph.sapling.color, ref:'trees' }),
        mature:  new graphic({ name:"mature trees", color:Prm.graph.mature .color, ref:'trees' }),
        elder:   new graphic({ name:"elder trees" , color:Prm.graph.elder  .color, ref:'trees' })
    };
    Gg.legend = document.getElementById ('legend');
    for (g in Gg.graphs)
    {
        var gr = Gg.graphs[g];
        var c = create_counter (gr);
        gr.counter = c.counter;
        Gg.legend.appendChild (c.item);
    }

    // initialize counters
    var counters = document.getElementById ('counters');
    var def = [ "mauling", "cutting" ];
    var d; for (d in def)
    {
        var c = create_counter ({ name:def[d], color:Prm.graph[def[d]].color });
        counters.appendChild (c.item);
        Gg.counter[def[d]] = c.counter;
    }

    // initialize log
    Gg.log = document.getElementById ('log');

    // create our forest
    create_forest(Prm.forest_size);
    start();
}

function create_forest (size)
{
    if (size < 2) size = 2;
    if (size > 100) size = 100;
    Forest = new forest (size);
    Prm.icon_size = Prm.canvas_f_size / size;
    Gf.ctx.font = 'Bold '+Prm.icon_size+'px Arial';
    Forest.populate ();
    draw_forest();
    var g; for (g in Gg.graphs) Gg.graphs[g].reset();
    draw_graphs();
}

function animate()
{
    if (Gf.tick % Prm.tick == 0)
    {
        Forest.tick();
        draw_graphs();
    }
    draw_forest();
    Gf.tick+= Gf.period;
    if (Gf.tick == Gf.stop_date) stop();
}

function draw_forest ()
{
    function draw_dweller (dweller)
    {
        var type = Prm.graph[dweller.type];
        Gf.ctx.fillStyle = type.color;
        var x = dweller.x * time_fraction + dweller.old_x * (1 - time_fraction);
        var y = dweller.y * time_fraction + dweller.old_y * (1 - time_fraction);
        Gf.ctx.fillText (type.pic, x * Prm.icon_size, (y+1) * Prm.icon_size);
    }

    function draw_event (evt)
    {
        var gr = Prm.graph[evt.type];
        Gf.ctx.fillStyle = gr.color;
        Gf.ctx.fillText (gr.pic, evt.x * Prm.icon_size, (evt.y+1) * Prm.icon_size);
    }

    function draw_tree (tree)
    {
        // trees grow from one category to the next
        var type = Prm.graph[tree.name];
        var next = Prm.graph[type.next];
        var radius = (type.radius + (next.radius - type.radius) / Prm.age[type.next] * tree.age) * Prm.icon_size;
        Gf.ctx.fillStyle = Prm.graph[tree.name].color;
        Gf.ctx.beginPath();
        Gf.ctx.arc((tree.x+.5) * Prm.icon_size, (tree.y+.5) * Prm.icon_size, radius, 0, 2*Math.PI);
        Gf.ctx.fill();
    }

    // background
    Gf.ctx.fillStyle = Prm.graph.soil.color;
    Gf.ctx.fillRect (0, 0, Gf.canvas.width, Gf.canvas.height);

    // time fraction to animate displacements
    var time_fraction = (Gf.tick % Prm.tick) / (Prm.tick-Gf.period);

    // entities
    Forest.trees.list.forEach (draw_tree);
    Forest.jacks.list.forEach (draw_dweller);
    Forest.bears.list.forEach (draw_dweller);
    Forest.events.forEach (draw_event);
}

// --------------------------------------------------------------------------------------------------
// Graphs
// --------------------------------------------------------------------------------------------------
function graphic (prm)
{
    this.name  = prm.name  || '?';
    this.color = prm.color || '#FFF';
    this.size  = prm.size  || 720;
    this.ref   = prm.ref;
    this.values = [];
    this.counter = document.getElement
}

graphic.prototype = {
    draw: function ()
    {
        Gg.ctx.strokeStyle = this.color;
        Gg.ctx.beginPath();
        for (var i = 0 ; i != this.values.length ; i++)
        {
            var x = (i + this.size - this.values.length) / this.size * Gg.canvas.width;
            var y = (1-(this.values[i] - this.min) / this.rng)       * Gg.canvas.height;

            if (i == 0) Gg.ctx.moveTo (x, y);
            else        Gg.ctx.lineTo (x, y);
        }
        Gg.ctx.stroke();
    },

    add: function (value)
    {
        // store value
        this.values.push (value);
        this.counter.innerHTML = value;

        // cleanup history
        while (this.values.length > this.size) this.values.splice (0,1);

        // compute min and max
        this.min = Math.min.apply(Math, this.values);
        if (this.min > 0) this.min = 0;
        this.max = this.ref 
                 ? Gg.graphs[this.ref].max
                 : Math.max.apply(Math, this.values);
        this.rng = this.max - this.min;
        if (this.rng == 0) this.rng = 1;
    },

    reset: function()
    {
        this.values = [];
    }
}

function draw_graphs ()
{
    function draw_graph (graph)
    {
        graph.draw();
    }

    // background
    Gg.ctx.fillStyle = '#000';
    Gg.ctx.fillRect (0, 0, Gg.canvas.width, Gg.canvas.height);

    // graphs
    var g; for (g in Gg.graphs)
    {
        var gr = Gg.graphs[g];
        gr.draw();
    }
}

// --------------------------------------------------------------------------------------------------
// User interface
// --------------------------------------------------------------------------------------------------
function set_tick(value)
{
    value = Math.round (value / Gf.period);
    if (value < 2) value = 2;
    value *= Gf.period;
    Prm.tick = value;
    return value;
}

function start (duration)
{
    if (Prm.timer) stop();
    Gf.stop_date = duration ? Gf.tick + duration*Prm.tick : -1;
    Prm.timer = setInterval (animate, Gf.period);
}

function stop ()
{
    if (Prm.timer)
    {
        clearInterval (Prm.timer);
        Prm.timer = null;
    }
    Gf.stop_date = -1;
}

</script>
</body>

อะไรต่อไป

ข้อสังเกตเพิ่มเติมยังคงยินดีต้อนรับ

NB: ฉันรู้ว่าต้นอ่อน / ผู้ใหญ่ / ผู้อาวุโสนับต้นไม้ยังคงยุ่งเหยิงเล็กน้อย

นอกจากนี้ฉันพบ document.getElementById อ่านได้มากกว่า $ ดังนั้นไม่จำเป็นต้องบ่นเกี่ยวกับการขาด jQueryisms มันฟรีโดย jQuery ถึงตัวเขาเองใช่ไหม?


+1, นี่มันดูไม่ดี, ฉันชอบที่จะเห็นการโต้ตอบ!
William Barbosa

ฉันมองไม่เห็นหมีและคนตัดไม้ ฉันควรทำอย่างไรที่จะเห็นพวกเขา? แต่ควรมีหมีมากที่สุดเพียงหนึ่งตัวต่อปีใช่ไหม
justhalf

@WilliamBarbosa ถ้า vox populi ส่งเสียงดังมากพอมันอาจเกลี้ยกล่อมให้ฉันเพิ่มบางอย่างลงไป :)

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

1
ต้นไม้ที่กำลังเติบโตนั้นมหัศจรรย์ เยี่ยมมาก!
Blackhole

14

AngularJS

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

สกรีนช็อตของป่า

สาธิต

<div ng-app="ForestApp" ng-controller="ForestController">
    <form name="parametersForm" ng-hide="evolutionInProgress" autocomplete="off" novalidate>
        <div class="line">
            <label for="forestSize">Size of the forest:</label>
            <input type="number" ng-model="Parameters.forestSize" id="forestSize" min="5" ng-pattern="/^[0-9]+$/" required />
        </div>
        <div class="line">
            <label for="simulationInterval">Number of milliseconds between each tick</label>
            <input type="number" ng-model="Parameters.simulationInterval" id="simulationInterval" min="10" ng-pattern="/^[0-9]+$/" required />
        </div>
        <div class="line">
            <label for="animationsEnabled">Animations enabled?
                <br /><small>(>= 300 ms between each tick is advisable)</small>

            </label>
            <input type="checkbox" ng-model="Parameters.animationsEnabled" id="animationsEnabled" />
        </div>
        <div class="line">
            <button ng-disabled="parametersForm.$invalid || evolutionInProgress" ng-click="launchEvolution()">Launch the evolution!</button>
        </div>
    </form>
    <div id="forest" ng-style="{width: side = (20*Parameters.forestSize) + 'px', height: side, 'transition-duration': transitionDuration = (Parameters.animationsEnabled ? 0.8*Parameters.simulationInterval : 0) + 'ms', '-webkit-transition-duration': transitionDuration}">
        <div ng-repeat="bear in Forest.bearsList" class="entity entity--bear" ng-style="{left: (20*bear.x) + 'px', top: (20*bear.y) + 'px'}"></div>
        <div ng-repeat="lumberjack in Forest.lumberjacksList" class="entity entity--lumberjack" ng-style="{left: (20*lumberjack.x) + 'px', top: (20*lumberjack.y) + 'px'}"></div>
        <div ng-repeat="tree in Forest.treesList" class="entity entity--tree" ng-class="'entity--tree--' + tree.stage" ng-style="{left: (20*tree.x) + 'px', top: (20*tree.y) + 'px'}"></div>
    </div>
    <div class="line"><em>Age of the forest:</em><samp>{{floor(Forest.age/12)}} year{{floor(Forest.age/12) > 1 ? 's' : ''}} and {{Forest.age%12}} month{{Forest.age%12 > 1 ? 's' : ''}}</samp></div>
    <div class="line"><em>Number of bears:</em><samp>{{Forest.bearsList.length}}</samp></div>
    <div class="line"><em>Number of lumberjacks:</em><samp>{{Forest.lumberjacksList.length}}</samp></div>
    <br />
    <div class="line"><em>Number of lumbers collected:</em><samp>{{Forest.numberOfLumbers}}</samp></div>
    <div class="line"><em>Number of mauls:</em><samp>{{Forest.numberOfMauls}}</samp></div>
</div>
/** @link http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array */
function shuffle(array) {
    var currentIndex = array.length,
        temporaryValue, randomIndex;

    // While there remain elements to shuffle...
    while (0 !== currentIndex) {

        // Pick a remaining element...
        randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex -= 1;

        // And swap it with the current element.
        temporaryValue = array[currentIndex];
        array[currentIndex] = array[randomIndex];
        array[randomIndex] = temporaryValue;
    }

    return array;
}

var forestApp = angular.module('ForestApp', ['ngAnimate']);

forestApp.value('Parameters', {
    /** @var int[] Maximal number of moves by species */
    speed: {
        bear: 5,
        lumberjack: 3,
    },

    /** @var int[] Initial percentage of each species in the forest */
    initialPercentage: {
        bear: 2,
        lumberjack: 10,
        tree: 50,
    },

    /** @var int[] Spawing rate, in percentage, of new saplings around an existing tree */
    spawningPercentage: {
        0: 0,
        1: 10,
        2: 20,
    },

    /** @var int[] Age of growth for an existing tree */
    ageOfGrowth: {
        sapling: 12,
        tree: 120,
    },

    /** @var int[] Lumber collected on an existing tree */
    numberOfLumbers: {
        tree: 1,
        elderTree: 2,
    },

    /** @var int Size of each side of the forest */
    forestSize: 20,

    /** @var int Number of milliseconds between each tick (month in the forest) */
    simulationInterval: 50,
});

forestApp.constant('TREE_STAGE', {
    SAPLING: 0,
    TREE: 1,
    ELDER_TREE: 2,
});

forestApp.factory('Tree', ['Forest', 'Parameters', 'TREE_STAGE', function (Forest, Parameters, TREE_STAGE) {
    // Classes which represents a tree
    var Tree = function (stage, x, y) {
        /** @var TREE_STAGE Current stage of the tree */
        this.stage = stage;

        /** @var int Current age of the tree, in month */
        this.age = 0;

        /** @var int X coordinates of the tree */
        this.x = x;

        /** @var int Y coordinates of the tree */
        this.y = y;

        this.tick = function () {
            if (Math.random() < Parameters.spawningPercentage[this.stage] / 100) {
                var freePositionsList = shuffle(Forest.getFreePositionsAround(this.x, this.y));
                if (freePositionsList.length > 0) {
                    var saplingPosition = freePositionsList[0];

                    Tree.create(TREE_STAGE.SAPLING, saplingPosition[0], saplingPosition[1]);
                }
            }

            ++this.age;

            if (this.stage === TREE_STAGE.SAPLING && this.age == Parameters.ageOfGrowth.sapling) {
                this.stage = TREE_STAGE.TREE;
            } else if (this.stage === TREE_STAGE.TREE && this.age == Parameters.ageOfGrowth.tree) {
                this.stage = TREE_STAGE.ELDER_TREE;
            }
        };

        /**
         * Remove the entity
         */
        this.remove = function () {
            var index = Forest.treesList.indexOf(this);
            Forest.treesList.splice(index, 1);
        };
    };

    Tree.create = function (stage, x, y) {
        Forest.add.tree(new Tree(stage, x, y));
    };

    return Tree;
}]);

forestApp.factory('Lumberjack', ['Forest', 'Parameters', 'TREE_STAGE', function (Forest, Parameters, TREE_STAGE) {
    // Classes which represents a lumberjack
    var Lumberjack = function (x, y) {
        /** @var int X coordinates of the lumberjack */
        this.x = x;

        /** @var int Y coordinates of the lumberjack */
        this.y = y;

        this.tick = function () {
            for (movement = Parameters.speed.lumberjack; movement > 0; --movement) {
                var positionsList = shuffle(Forest.getPositionsAround(this.x, this.y));
                var newPosition = positionsList[0];
                this.x = newPosition[0];
                this.y = newPosition[1];

                var tree = Forest.getTreeAt(this.x, this.y);
                if (tree !== null) {
                    if (tree.stage === TREE_STAGE.SAPLING) {
                        return;
                    } else if (tree.stage === TREE_STAGE.TREE) {
                        Forest.numberOfLumbers += Parameters.numberOfLumbers.tree;
                    } else {
                        Forest.numberOfLumbers += Parameters.numberOfLumbers.elderTree;
                    }

                    tree.remove();
                    movement = 0;
                };
            }
        };

        /**
         * Remove the entity
         */
        this.remove = function () {
            if (Forest.lumberjacksList.length === 1) {
                this.x = Math.floor(Math.random() * Parameters.forestSize);
                this.y = Math.floor(Math.random() * Parameters.forestSize);
            } else {
                var index = Forest.lumberjacksList.indexOf(this);
                Forest.lumberjacksList.splice(index, 1);
            }
        };
    };

    Lumberjack.create = function (x, y) {
        Forest.add.lumberjack(new Lumberjack(x, y));
    };

    return Lumberjack;
}]);

forestApp.factory('Bear', ['Forest', 'Parameters', function (Forest, Parameters) {
    // Classes which represents a bear
    var Bear = function (x, y) {
        /** @var int X coordinates of the bear */
        this.x = x;

        /** @var int Y coordinates of the bear */
        this.y = y;

        this.tick = function () {
            for (movement = Parameters.speed.bear; movement > 0; --movement) {
                var positionsList = shuffle(Forest.getPositionsAround(this.x, this.y));
                var newPosition = positionsList[0];
                this.x = newPosition[0];
                this.y = newPosition[1];

                angular.forEach(Forest.getLumberjacksListAt(this.x, this.y), function (lumberjack) {
                    lumberjack.remove();
                    ++Forest.numberOfMauls;
                    movement = 0;
                });
            }
        };

        /**
         * Remove the entity
         */
        this.remove = function () {
            var index = Forest.bearsList.indexOf(this);
            Forest.bearsList.splice(index, 1);
        };
    };

    Bear.create = function (x, y) {
        Forest.add.bear(new Bear(x, y));
    };

    return Bear;
}]);

forestApp.service('Forest', ['Parameters', function (Parameters) {
    var forest = this;

    this.age = 0;
    this.numberOfLumbers = 0;
    this.numberOfMauls = 0;

    this.bearsList = [];
    this.lumberjacksList = [];
    this.treesList = [];

    this.getEntitiesList = function () {
        return forest.bearsList.concat(forest.lumberjacksList, forest.treesList);
    };

    /**
     * Age the forest by one month
     */
    this.tick = function () {
        angular.forEach(forest.getEntitiesList(), function (entity) {
            entity.tick();
        });

        ++forest.age;
    };

    this.add = {
        bear: function (bear) {
            forest.bearsList.push(bear);
        },
        lumberjack: function (lumberjack) {
            forest.lumberjacksList.push(lumberjack);
        },
        tree: function (tree) {
            forest.treesList.push(tree);
        },
    };

    /**
     * @return Tree|null Tree at this position, or NULL if there is no tree.
     */
    this.getTreeAt = function (x, y) {
        var numberOfTrees = forest.treesList.length;
        for (treeId = 0; treeId < numberOfTrees; ++treeId) {
            var tree = forest.treesList[treeId];
            if (tree.x === x && tree.y === y) {
                return tree;
            }
        }

        return null;
    };

    /**
     * @return Lumberjack[] List of the lumberjacks at this position
     */
    this.getLumberjacksListAt = function (x, y) {
        var lumberjacksList = [];
        angular.forEach(forest.lumberjacksList, function (lumberjack) {
            if (lumberjack.x === x && lumberjack.y === y) {
                lumberjacksList.push(lumberjack);
            }
        });

        return lumberjacksList;
    };

    /**
     * @return int[] Positions around this position
     */
    this.getPositionsAround = function (x, y) {
        var positionsList = [
            [x - 1, y - 1],
            [x, y - 1],
            [x + 1, y - 1],
            [x - 1, y],
            [x + 1, y],
            [x - 1, y + 1],
            [x, y + 1],
            [x + 1, y + 1]
        ];

        return positionsList.filter(function (position) {
            return (position[0] >= 0 && position[1] >= 0 && position[0] < Parameters.forestSize && position[1] < Parameters.forestSize);
        });
    };

    /**
     * @return int[] Positions without tree around this position
     */
    this.getFreePositionsAround = function (x, y) {
        var positionsList = forest.getPositionsAround(x, y);

        return positionsList.filter(function (position) {
            return forest.getTreeAt(position[0], position[1]) === null;
        });
    };
}]);

forestApp.controller('ForestController', ['$interval', '$scope', 'Bear', 'Forest', 'Lumberjack', 'Parameters', 'Tree', 'TREE_STAGE', function ($interval, $scope, Bear, Forest, Lumberjack, Parameters, Tree, TREE_STAGE) {
    $scope.Forest = Forest;
    $scope.Parameters = Parameters;
    $scope.evolutionInProgress = false;
    $scope.floor = Math.floor;

    var positionsList = [];

    /**
     * Start the evolution of the forest
     */
    $scope.launchEvolution = function () {
        $scope.evolutionInProgress = true;

        for (var x = 0; x < Parameters.forestSize; ++x) {
            for (var y = 0; y < Parameters.forestSize; ++y) {
                positionsList.push([x, y]);
            }
        }

        shuffle(positionsList);
        var numberOfBears = Parameters.initialPercentage.bear * Math.pow(Parameters.forestSize, 2) / 100;
        for (var bearId = 0; bearId < numberOfBears; ++bearId) {
            Bear.create(positionsList[bearId][0], positionsList[bearId][1]);
        }

        shuffle(positionsList);
        var numberOfLumberjacks = Parameters.initialPercentage.lumberjack * Math.pow(Parameters.forestSize, 2) / 100;
        for (var lumberjackId = 0; lumberjackId < numberOfLumberjacks; ++lumberjackId) {
            Lumberjack.create(positionsList[lumberjackId][0], positionsList[lumberjackId][1]);
        }

        shuffle(positionsList);
        var numberOfTrees = Parameters.initialPercentage.tree * Math.pow(Parameters.forestSize, 2) / 100;
        for (var treeId = 0; treeId < numberOfTrees; ++treeId) {
            Tree.create(TREE_STAGE.TREE, positionsList[treeId][0], positionsList[treeId][1]);
        }

        $interval(function () {
            Forest.tick();

            if (Forest.age % 12 === 0) {
                // Hire or fire lumberjacks
                if (Forest.numberOfLumbers >= Forest.lumberjacksList.length) {
                    shuffle(positionsList);
                    var numberOfLumberjacks = Math.floor(Forest.numberOfLumbers / Forest.lumberjacksList.length);
                    for (var lumberjackId = 0; lumberjackId < numberOfLumberjacks; ++lumberjackId) {
                        Lumberjack.create(positionsList[lumberjackId][0], positionsList[lumberjackId][1]);
                    }
                } else {
                    shuffle(Forest.lumberjacksList);
                    Forest.lumberjacksList[0].remove();
                }

                // Hire or fire bears
                if (Forest.numberOfMauls === 0) {
                    shuffle(positionsList);
                    Bear.create(positionsList[0][0], positionsList[0][1]);
                } else {
                    Forest.bearsList[0].remove();
                }

                Forest.numberOfLumbers = 0;
                Forest.numberOfMauls = 0;
            }

        }, Parameters.simulationInterval);
    };
}]);

1
นอกจากนี้ฉันชอบวิธีที่เราใช้รหัสเดียวกันในการสุ่มอาเรย์สุ่ม
Kevin L

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

getEntitiesAtดูเหมือนจะเป็นซีพียูหมู! ใช้งานระบบด้วยกริด 50x50 ใช้เวลามากกว่าหนึ่งวินาทีต่อเดือนบนพีซีของฉัน นอกจากนี้ยังมีกรณีที่ต้นไม้ทุกต้นถูกสับจากนั้นต้นไม้ทั้งหมดจะถูกไล่ออกและแผนที่จะค่อยๆเต็มไปด้วยหมี :) ลองขนาดเล็ก (10 หรือน้อยกว่า) เพื่อดูว่าเกิดขึ้น

@ kuroineko: เป็นเพราะมีข้อผิดพลาด (หรือค่อนข้างเป็นกฎที่ไม่ได้ดำเนินการ) ซึ่งหลังจากที่คนตัดไม้ถูกขย้ำและหากเกิดขึ้นใหม่กับหมีมันจะไม่อยู่ที่นั่น ดังนั้นจึงได้รับการ 0. แก้ไขปัญหากับการตรวจสอบหลังจากที่Forest.tick()ถ้าแล้วForest.lumberjackList.length == 0 Lumberjack.create(<number>, <number>)
justhalf

1
ฉันได้ลองปรับแต่งรหัสของคุณเพื่อให้หมีชอบอยู่ในป่า (และยังรวมถึงเกณฑ์การกำจัดหมีเหมือน @ kuroineko เช่นเพิ่มหมีหากมีการลอกคราบน้อยกว่า 10% และลบหมีถ้ามีการลอกคราบมากกว่า 15% ) เป็นที่น่าสนใจที่จะเห็นว่าพฤติกรรมเปลี่ยนไปอย่างไร =)
justhalf

3

จาวาสคริ

ฉันคิดว่ามันใช้งานได้เป็นส่วนใหญ่ มีพฤติกรรมบางอย่างที่บ้าคลั่งที่ฉันวางไข่ใหม่หมี / ไม้แปรรูปในซิงค์และขวาถัดจากกันและกันเพราะความเกียจคร้านในการแทรก

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

ซอ

HTML:

<canvas id="c" width="1" height="1"></canvas>
<div id="p1"></div>
<div id="p2"></div>
<div id="p3"></div>
<div id="p4"></div>

js:

var n = 10; // Size of the grid
var drawUsingColor = true; // If true, draws colors for each entity instead :D
var intervalTime = 1000; // how often each tick happens, in milliseconds
var jackRatio = 0.1;
var treeRatio = 0.5;
var bearRatio = 0.02;
var size = 48; // Pixels allocated (in size x size) for each entity
var font = "30px Lucida Console"; // if drawUsingColor is false

var bearColor = '#8B4513'; // Saddlebrown
var elderColor = '#556B2F'; // DarkOliveGreen
var lumberjackColor = '#B22222'; // Firebrick
var treeColor = '#008000'; // Green
var saplingColor = '#ADFF2F'; // GreenYellow

// Game rules:
var spawnSaplingChance = 0.1;
var elderTreeAge = 120;
var elderSaplingChance = 0.2;
var treeAge = 12;
var lumberjackRange = 3;
var bearRange = 5;
var zooPeriod = 12; // If a maul happens within this period
var lumberPeriod = 12; // New lumberjacks hired in this period


var time = 1;

var world;
var n2 = n * n; //because one saved keystroke
var zooqueue = [];
var lumberqueue = [];
var canvas = document.getElementById('c'); // Needs more jquery
var context = canvas.getContext('2d');
context.font = font;

// various statistics
var treesAlive = 0;
var jacksAlive = 0;
var bearsAlive = 0;
var currentLumber = 0;
var lumberjacksMauled = 0;
var recentEvents = '';

// Entity is a bear, eldertree, lumberjack, tree, sapling, with age. aka belts.
function Entity(belts, birthday) {
    this.type = belts;
    this.age = 0;
    this.birthday = birthday;
}

function initWorld() {
    canvas.height = size * n;
    canvas.width = size * n;
    world = new Array(n2);

    // One pass spawning algorithm: numEntity = number of entity left to spawn
    // If rand() in range [0,numtrees), spawn tree
    // if rand() in range [numtrees, numtrees+numjacks), spawn lumberjack
    // if rand() in range [numtrees+numjacks, numtrees+numjacks+numbears), spawn bear

    var numTrees = treeRatio * n2;
    var numJacks = jackRatio * n2;
    var numBears = bearRatio * n2;

    var godseed = new Array(n2);
    for (var i = 0; i < n2; i++) {
        godseed[i] = i;
    }
    shuffle(godseed);

    for (var i = 0; i < n2; i++) {
        var god = godseed.pop();
        if (god < numTrees) {
            world[i] = new Entity('T', 0);
            treesAlive++;
        } else if (god < numTrees + numJacks) {
            world[i] = new Entity('L', 0);
            jacksAlive++;
        } else if (god < numTrees + numJacks + numBears) {
            world[i] = new Entity('B', 0);
            bearsAlive++;
        }
        // console.log(world, i);
    }

    // populate zoo array, lumber array
    for (var i = 0; i < zooPeriod; i++) {
        zooqueue.push(0);
    }

    for (var i = 0; i < lumberPeriod; i++) {
        lumberqueue.push(0);
    }
}

animateWorld = function () {
    recentEvents = '';
    computeWorld();
    drawWorld();
    time++;

    $('#p1').text(treesAlive + ' trees alive');
    $('#p2').text(bearsAlive + ' bears alive');
    $('#p3').text(jacksAlive + ' lumberjacks alive');
    $('#p4').text(recentEvents);
};

function computeWorld() {
    zooqueue.push(lumberjacksMauled);
    lumberqueue.push(currentLumber);

    // Calculate entity positions
    for (var i = 0; i < n2; i++) {
        if (world[i]) {
            switch (world[i].type) {
                case 'B':
                    bearStuff(i);
                    break;
                case 'E':
                    elderStuff(i);
                    break;
                case 'L':
                    lumberjackStuff(i);
                    break;
                case 'T':
                    treeStuff(i);
                    break;
                case 'S':
                    saplingStuff(i);
                    break;
            }
        }
    }

    // Pop the # mauls from zooPeriod's ago, if lumberjacksMauled > oldmauls, then someone was eaten.
    var oldmauls = zooqueue.shift();
    if (time % zooPeriod === 0) {
        if (lumberjacksMauled > oldmauls) {
            if (remove('B') == 1) {
                bearsAlive--;
                recentEvents += 'Bear sent to zoo! ';
            }
        } else {
            bearsAlive++;
            spawn('B');
            recentEvents += 'New bear appeared! ';
        }
    }

    var oldLumber = lumberqueue.shift();
    if (time % lumberPeriod === 0) {
        // # lumberjack to hire
        var hire = Math.floor((currentLumber - oldLumber) / jacksAlive);
        if (hire > 0) {
            recentEvents += 'Lumber jack hired! (' + hire + ') ';
            while (hire > 0) {
                jacksAlive++;
                spawn('L');
                hire--;
            }
        } else {
            if (remove('L') == 1) {
                recentEvents += 'Lumber jack fired!  ';
                jacksAlive--;
            }
            else {
            }
        }
    }

    // Ensure > 1 lumberjack
    if (jacksAlive === 0) {
        jacksAlive++;
        spawn('L');
        recentEvent += 'Lumberjack spontaneously appeared';
    }
}

// Not the job of spawn/remove to keep track of whatever was spawned/removed
function spawn(type) {
    var index = findEmpty(type);
    if (index != -1) {
        world[index] = new Entity(type, time);
    }
    // recentEvents += 'Spawned a ' + type + '\n';
}

function remove(type) {
    var index = findByType(type);
    if (index != -1) {
        world[index] = null;
        return 1;
    }
    return -1;
    // recentEvents += 'Removed a ' + type + '\n';
}

// Searches in world for an entity with type=type. Currently implemented as
// linear scan, which isn't very random
function findByType(type) {
    for (var i = 0; i < n2; i++) {
        if (world[i] && world[i].type == type) return i;
    }
    return -1;
}

// Also linear scan 
function findEmpty(type) {
    for (var i = 0; i < n2; i++) {
        if (!world[i]) {
            return i;
        }
    }
    return -1;
}

function bearStuff(index) {
    if (world[index].birthday == time) {
        return;
    }

    // Wander around
    var tindex = index;
    for (var i = 0; i < lumberjackRange; i++) {
        var neighbors = get8Neighbor(tindex);

        var mov = neighbors[Math.floor(Math.random() * neighbors.length)];
        if (world[mov] && world[mov].type == 'L') {
            recentEvents += 'Bear (' + index % 10 + ',' + Math.floor(index / 10) + ') mauled a Lumberjack (' + mov % 10 + ',' + Math.floor(mov / 10) + ') !';
            lumberjacksMauled++;
            jacksAlive--;
            world[mov] = new Entity('B', time);
            world[mov].age = ++world[index].age;
            world[index] = null;
            return;
        }
        tindex = mov;
    }

    if (!world[tindex]) {
        world[tindex] = new Entity('B', time);
        world[tindex].age = ++world[index].age;
        world[index] = null;
    }
}

function elderStuff(index) {
    if (world[index].birthday == time) {
        return;
    }
    neighbors = get8Neighbor(index);

    // spawn saplings
    for (var i = 0; i < neighbors.length; i++) {
        if (!world[neighbors[i]]) {
            if (Math.random() < elderSaplingChance) {
                world[neighbors[i]] = new Entity('S', time);
                treesAlive++;
            }
        }
    }

    // become older
    world[index].age++;
}

function lumberjackStuff(index) {
    if (world[index].birthday == time) {
        return;
    }

    // Wander around
    var tindex = index;
    for (var i = 0; i < lumberjackRange; i++) {
        var neighbors = get8Neighbor(tindex);

        var mov = neighbors[Math.floor(Math.random() * neighbors.length)];
        if (world[mov] && (world[mov].type == 'T' || world[mov].type == 'E')) {
            world[mov].type == 'T' ? currentLumber++ : currentLumber += 2;
            treesAlive--;
            world[mov] = new Entity('L', time);
            world[mov].age = ++world[index].age;
            world[index] = null;
            return;
        }
        tindex = mov;
    }

    if (!world[tindex]) {
        world[tindex] = new Entity('L', time);
        world[tindex].age = ++world[index].age;
        world[index] = null;
    }
}

function treeStuff(index) {
    if (world[index].birthday == time) {
        return;
    }
    neighbors = get8Neighbor(index);

    // spawn saplings
    for (var i = 0; i < neighbors.length; i++) {
        if (!world[neighbors[i]]) {
            if (Math.random() < spawnSaplingChance) {
                world[neighbors[i]] = new Entity('S', time);
                treesAlive++;
            }
        }
    }

    // promote to elder tree?
    if (world[index].age >= elderTreeAge) {
        world[index] = new Entity('E', time);
        return;
    }

    // become older
    world[index].age++;
}

function saplingStuff(index) {
    if (world[index].birthday == time) {
        return;
    }

    // promote to tree?
    if (world[index].age > treeAge) {
        world[index] = new Entity('T', time);
        return;
    }
    world[index].age++;
}

// Returns array containing up to 8 valid neighbors.
// Prolly gonna break for n < 3 but oh well
function get8Neighbor(index) {
    neighbors = [];

    if (index % n != 0) {
        neighbors.push(index - n - 1);
        neighbors.push(index - 1);
        neighbors.push(index + n - 1);
    }
    if (index % n != n - 1) {
        neighbors.push(index - n + 1);
        neighbors.push(index + 1);
        neighbors.push(index + n + 1);
    }
    neighbors.push(index - n);
    neighbors.push(index + n);
    return neighbors.filter(function (val, ind, arr) {
        return (0 <= val && val < n2)
    });
}

// Each entity allocated 5x5px for their art
function drawWorld() {
    context.clearRect(0, 0, canvas.width, canvas.height);
    for (var i = 0; i < n2; i++) {
        if (world[i]) {
            var x = i % n;
            var y = Math.floor(i / n);
            switch (world[i].type) {
                case 'B':
                    drawBear(x, y);
                    break;
                case 'E':
                    drawElder(x, y);
                    break;
                case 'L':
                    drawJack(x, y);
                    break;
                case 'T':
                    drawTree(x, y);
                    break;
                case 'S':
                    drawSapling(x, y);
                    break;
            }
        }
    }
}

function drawBear(x, y) {
    if (drawUsingColor) {
        drawRect(x * size, y * size, size, size, bearColor);
    } else {
        drawLetter(x * size, y * size, 'B');
    }
}

function drawElder(x, y) {
    if (drawUsingColor) {
        drawRect(x * size, y * size, size, size, elderColor);
    } else {
        drawLetter(x * size, y * size, 'E');
    }
}

function drawJack(x, y) {
    if (drawUsingColor) {
        drawRect(x * size, y * size, size, size, lumberjackColor);
    } else {
        drawLetter(x * size, y * size, 'J');
    }
}

function drawTree(x, y) {
    if (drawUsingColor) {
        drawRect(x * size, y * size, size, size, treeColor);
    } else {
        drawLetter(x * size, y * size, 'T');
    }
}

function drawSapling(x, y) {
    if (drawUsingColor) {
        drawRect(x * size, y * size, size, size, saplingColor);
    } else {
        drawLetter(x * size, y * size, 'S');
    }
}

function drawLine(x1, y1, x2, y2, c) {
    context.beginPath();
    context.moveTo(x1, y1);
    context.lineTo(x2, y2);
    context.lineWidth = 3;
    context.strokeStyle = c;
    context.stroke();
}

function drawRect(x, y, w, h, c) {
    context.fillStyle = c;
    context.fillRect(x, y, w, h);
}

function drawLetter(x, y, l) {
    context.fillText(l, x, y);
}

$(document).ready(function () {
    initWorld();
    intervalID = window.setInterval(animateWorld, intervalTime);
    /*$('#s').click(function() {
        animateWorld();
    })*/
});

// http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
function shuffle(array) {
    var currentIndex = array.length,
        temporaryValue, randomIndex;

    // While there remain elements to shuffle...
    while (0 !== currentIndex) {

        // Pick a remaining element...
        randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex -= 1;

        // And swap it with the current element. 
        temporaryValue = array[currentIndex];
        array[currentIndex] = array[randomIndex];
        array[randomIndex] = temporaryValue;
    }
    return array;
}

มีบางอย่างแปลก ๆ กับฟอเรสต์ขนาดใหญ่ (ลองn = 50เป็นต้น)
Blackhole

@Blackhole บางสิ่งบางอย่างที่แปลก = ____?
Kevin L


ฉันสังเกตเห็นหมีกำลังติดและป่าที่ยึดครองส่วนล่างของแผนที่ตั้งแต่ฉันวางไข่ lumberjacks ในส่วนบนของแผนที่
Kevin L

1
มีบางอย่างผิดปกติกับวิธีการที่หมีใหม่และคนตัดไม้วางไข่ ฉันคิดว่าการวางไว้ที่มุมซ้ายบนจะเปลี่ยนความสมดุลทั่วโลกของระบบได้เล็กน้อย

3

หลาม

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

ฉันให้ชื่อสุ่มกับคนตัดไม้และหมี ต้นไม้คือiหลังจากIนั้น#Lumberjacks เป็นxหมีอยู่o

import os

from random import randint, choice, shuffle
from time import sleep

NGRID = 15
SLEEPTIME = 0.0125
DURATION = 4800
VERBOSE = True

###init
grid = [[[] for _ in range(NGRID)] for _ in range(NGRID)]
#Money earned this year
n_lumbers = 0
#Lumberjacks killed this year
n_maul = 0

tick = 0
events = []
#total number of
d_total = {'trees':0,
           'lumberjacks': 0,
           'bears': 0,
           'cut': 0,
           'maul': 0,
           'capture': 0,
           'lumbers': 0,
           'fired': 0}

d_oldest = {'tree': 0,
            'lumberjack': (0, ""),
            'bear': (0, "")}

d_most = {'n_maul': (0, ""),
          'n_lumber': (0, ""),
          'n_cut': (0, "")}

d_year = {'n_maul': 0,
          'n_lumber': 0}

###Classes
class Tree(object):
    """Represent a Sapling, Tree, or Elder Tree"""
    def __init__(self, coords, m=0, t='Sapling'):
        self.months = m
        self.typeof = t
        self.coords = coords
    def grow(self, m=1):
        """the tree grows 1 month and its type might change"""
        self.months = self.months + m
        if self.months == 12:
            self.typeof = 'Tree'
        elif self.months == 480:
            self.typeof = 'Elder Tree'
    def __str__(self):
        if self.typeof == 'Sapling':
            return 'i'
        elif self.typeof == 'Tree':
            return 'I'
        else:
            return '#'

class Animated(object):
    """Animated beings can move"""
    def __init__(self, coords):
        self.coords = coords
        self.old_coords = None
        self.months = 0
    def where(self):
        return c_neighbors(self.coords)
    def choose_new_coords(self):
        self.old_coords = self.coords
        possible = self.where()
        if possible:
            direction = choice(self.where())
            self.coords = [(self.coords[i]+direction[i]) % NGRID for i in range(2)]
#    def __del__(self):
#        print "died at "+ str(self.coords)


class Lumberjack(Animated):
    """Lumberjacks chop down trees"""
    def __init__(self, coords):
        super(Lumberjack, self).__init__(coords)
        self.nb_cut = 0
        self.nb_lumber = 0
        self.name = gen_name("l")
    def __str__(self):
        return "x"

class Bear(Animated):
    """Bears maul"""
    def __init__(self, coords):
        super(Bear, self).__init__(coords)
        self.nb_maul = 0
        self.name = gen_name("b")
    def where(self):
        return c_land_neighbors(self.coords)
    def __str__(self):
        return "o"

###list of coords
def c_neighbors(coords):
    """returns the list of coordinates of adjacent cells"""
    return [[(coords[0] + i) % NGRID, (coords[1] + j) % NGRID] \
            for i in [-1, 0, 1] \
            for j in [-1, 0, 1] \
            if (i,j) != (0, 0)]

def c_empty_neighbors(coords):
    """returns the list of coordinates of adjacent cells that are empty """
    return [[i, j] for [i,j] in c_neighbors(coords) if grid[i][j] == []]

def c_land_neighbors(coords):
    """returns the list of coordinates of adjacent cells that contain not Trees
    for bears"""
    return [[i, j] for [i,j] in c_neighbors(coords)\
            if (grid[i][j] == []) or (not isinstance(grid[i][j][0], Tree))]

def c_empty_cells():
    """returns list of coords of empty cells in the grid"""
    return [[i, j] for i in range(NGRID) for j in range(NGRID) if grid[i][j] == []]

def c_not_bear_cells():
    """returns list of coords of cells without bear"""
    return [[i, j] for i in range(NGRID) for j in range(NGRID) \
            if not isinstance(grid[i][j], Bear)]

###one less
def maul(lumberjack):
    """a lumberjack will die"""
    global n_maul
    n_maul = n_maul + 1
    d_total['maul'] = d_total['maul'] + 1
    remove_from_grid(lumberjack.coords, lumberjack)
    return lumberjack.name + " is sent to hospital" + check_lumberjacks()

def capture_bear():
    """too many mauls, a Zoo traps a bear"""
    d_total['capture'] = d_total['capture'] + 1
    bear = choice(get_bears())
    remove_from_grid(bear.coords, bear)
    return bear.name + " has been captured"

def fire_lumberjack():
    """the job is not done correctly, one lumberjack is let go"""
    d_total['fired'] = d_total['fired'] + 1
    lumberjack = choice(get_lumberjacks())
    remove_from_grid(lumberjack.coords, lumberjack)
    return lumberjack.name + " has been fired" + check_lumberjacks()

def remove_from_grid(coords, item):
    """remove item from the grid at the coords"""
    grid[coords[0]][coords[1]].remove(item)
    del item

###one more
def new_animate(class_):
    """a new lumberjack or bear joins the forest"""
    if class_==Bear:
        d_total['bears'] = d_total['bears'] + 1
        x, y = choice(c_empty_cells())
    else:
        d_total['lumberjacks'] = d_total['lumberjacks'] + 1
        x, y = choice(c_not_bear_cells())
    new_being = class_([x,y])
    grid[x][y].append(new_being)
    return "a new " + class_.__name__ + " enters the forest: " + new_being.name

def check_lumberjacks():
    """we will never reduce our Lumberjack labor force below 0"""
    if len(get_lumberjacks())==0:
        return " - no more lumberjack, " + new_animate(Lumberjack)
    return ""

###movements
def move_on_grid(being):
    [x, y] = being.old_coords
    grid[x][y].remove(being)
    [x, y] = being.coords
    grid[x][y].append(being)

def move_lumberjack(lumberjack):
    """Lumberjacks move 3 times if they don't encounter a (Elder) Tree or a Bear"""
    global n_lumbers
    for _ in range(3):
        lumberjack.choose_new_coords()
        move_on_grid(lumberjack)
        [x, y] = lumberjack.coords
        #is there something at the new coordinate?
        #move append so this lumberjack is at the end
        if grid[x][y][:-1] != []:
            if isinstance(grid[x][y][0], Tree):
                the_tree = grid[x][y][0]
                price = worth(the_tree)
                if price > 0:
                    lumberjack.nb_cut = lumberjack.nb_cut + 1
                    d_most['n_cut'] = max((lumberjack.nb_cut, lumberjack.name), \
                                           d_most['n_cut'])
                    d_total['cut'] = d_total['cut'] + 1
                    n_lumbers = n_lumbers + price
                    d_total['lumbers'] = d_total['lumbers'] + 1
                    lumberjack.nb_lumber = lumberjack.nb_lumber + price
                    d_most['n_lumber'] = max(d_most['n_lumber'], \
                                             (lumberjack.nb_lumber, lumberjack.name))
                    remove_from_grid([x, y], the_tree)
                    return lumberjack.name + " cuts 1 " + the_tree.typeof
            #if there is a bear, all lumberjacks have been sent to hospital
            if isinstance(grid[x][y][0], Bear):
                #the first bear is the killer
                b = grid[x][y][0]
                b.nb_maul = b.nb_maul + 1
                d_most['n_maul'] = max((b.nb_maul, b.name), d_most['n_maul'])
                return maul(lumberjack)
    return None

def move_bear(bear):
    """Bears move 5 times if they don't encounter a Lumberjack"""
    for _ in range(5):
        bear.choose_new_coords()
        move_on_grid(bear)
        [x, y] = bear.coords
        there_was_something = (grid[x][y][:-1] != [])
        if there_was_something:
            #bears wander where there is no tree
            #so it's either a lumberjack or another bear
            #can't be both.
            if isinstance(grid[x][y][0], Lumberjack):
                bear.nb_maul = bear.nb_maul + 1
                d_most['n_maul'] = max((bear.nb_maul, bear.name), \
                                       d_most['n_maul'])
                return maul(grid[x][y][0])
    return None

###get objects
def get_objects(class_):
    """get a list of instances in the grid"""
    l = []
    for i in range(NGRID):
        for j in range(NGRID):
          if grid[i][j]:
              for k in grid[i][j]:
                  if isinstance(k, class_):
                      l.append(k)
    return l

def get_trees():
    """list of trees"""
    return get_objects(Tree)

def get_bears():
    """list of bears"""
    return get_objects(Bear)

def get_lumberjacks():
    """list of lumberjacks"""
    return get_objects(Lumberjack)

###utils
def gen_name(which="l"):
    """generate random name"""
    name = ""
    for _ in range(randint(1,4)):
        name = name + choice("bcdfghjklmnprstvwxz") + choice("auiey")
    if which == "b":
        name = name[::-1]
    return name.capitalize()

def worth(tree):
    """pieces for a tree"""
    if tree.typeof == 'Elder Tree':
        return 2
    if tree.typeof == 'Tree':
        return 1
    return 0

def one_month():
    """a step of one month"""
    events = []
    global tick
    tick = tick + 1
    #each Tree can spawn a new sapling
    for t in get_trees():
        l_empty_spaces = c_empty_neighbors(t.coords)
        percent = 10 if t.typeof == 'Tree' else \
                  20 if t.typeof == 'Elder Tree' else 0
        if (randint(1,100) < percent):
            if l_empty_spaces:
                [x, y] = choice(l_empty_spaces)
                grid[x][y] = [Tree([x,y])]
                d_total['trees'] = d_total['trees'] + 1
        t.grow()
        d_oldest['tree'] = max(t.months, d_oldest['tree'])
    #each lumberjack/bear moves
    for l in get_lumberjacks():
        l.months = l.months + 1
        d_oldest['lumberjack'] = max((l.months, l.name), \
                                     d_oldest['lumberjack'])
        event = move_lumberjack(l)
        if event:
            events.append(event)
    for b in get_bears():
        b.months = b.months + 1
        d_oldest['bear'] = max((b.months, b.name), d_oldest['bear'])
        event = move_bear(b)
        if event:
            events.append(event)
    return events

def print_grid():
    """print the grid
    if more than 1 thing is at a place, print the last.
    At 1 place, there is
    - at most a tree and possibly several lumberjack
    - or 1 bear
    """
    print "-" * 2 * NGRID
    print '\n'.join([' '.join([str(i[-1]) if i != [] else ' ' \
                               for i in line]) \
                     for line in grid])
    print "-" * 2 * NGRID

def clean():
    """clear the console"""
    os.system('cls' if os.name == 'nt' else 'clear')

def print_grid_and_events():
    """print grid and list of events"""
    clean()
    print_grid()
    if VERBOSE:
        print '\n'.join(events)
        print "-" * 2 * NGRID

###populate the forest
l = c_empty_cells()
shuffle(l)
for x, y in l[:((NGRID*NGRID) / 2)]:
    grid[x][y] = [Tree([x, y], 12, 'Tree')]
    d_total['trees'] = d_total['trees'] + 1

l = c_empty_cells()
shuffle(l)
for x, y in l[:((NGRID*NGRID) / 10)]:
    grid[x][y] = [Lumberjack([x, y])]
    d_total['lumberjacks'] = d_total['lumberjacks'] + 1

l = c_empty_cells()
shuffle(l)
for x, y in l[:((NGRID*NGRID) / 10)]:
    grid[x][y] = [Bear([x, y])]
    d_total['bears'] = d_total['bears'] + 1

###time goes on
while (tick <= DURATION and len(get_trees())>0):
    events = one_month()
    #end of the year
    if (tick % 12)==0:
        events.append("End of the year")
        #lumber tracking
        nlumberjacks = len(get_lumberjacks())
        events.append(str(n_lumbers) + " lumbers VS " +\
                      str(nlumberjacks) + " Lumberjacks")
        if n_lumbers >= nlumberjacks:
            n_hire = n_lumbers/nlumberjacks
            events.append("we hire " + str(n_hire) +\
                          " new Lumberjack" + ("s" if (n_hire > 1) else ""))
            for _ in range(n_hire):
                events.append(new_animate(Lumberjack))
        else:
            events.append(fire_lumberjack())
        d_year['n_lumber'] = max(d_year['n_lumber'], n_lumbers)
        n_lumbers = 0
        #maul tracking
        events.append("maul this year: " + str(n_maul))
        if n_maul == 0:
            events.append(new_animate(Bear))
        else:
            events.append(capture_bear())
        d_year['n_maul'] = max(d_year['n_maul'], n_maul)
        n_maul = 0
    print_grid_and_events()
    sleep(SLEEPTIME)

print "-"*70
print "End of the game"
print "-"*70
print "month:" + str(tick - 1)
print "number of trees still alive: " + str(len(get_trees()))
print "number of lumberjacks still alive: " + str(len(get_lumberjacks()))
print "number of bears still alive: " + str(len(get_bears()))

print "-"*70
print "oldest Tree ever is/was: " + str(d_oldest['tree'])
print "oldest Lumberjack ever is/was: " + str(d_oldest['lumberjack'][0]) + \
    " yo " + d_oldest['lumberjack'][1]
print "oldest Bear ever is/was: " + str(d_oldest['bear'][0]) + \
    " yo " + d_oldest['bear'][1]
print "-"*70
print "max cut by a Lumberjack: " + str(d_most['n_cut'][0]) + \
    " by " + str(d_most['n_cut'][1])
print "max lumber by a Lumberjack: " + str(d_most['n_lumber'][0]) + \
    " by " + str(d_most['n_lumber'][1])
print "max maul by a Bear: " + str(d_most['n_maul'][0]) + \
    " by " + str(d_most['n_maul'][1])
print "-"*70
print "max lumber in a year: " + str(d_year['n_lumber'])
print "max maul in a year: " + str(d_year['n_maul'])
print "-"*70
print "Total of:"
for i, j in d_total.items():
    print i, str(j)

เอาต์พุตบางส่วน:

------------------------------
          x



    I
    I
  x   i                     I

      i i i   i       I I x i
i   I   I i I I   i i     o
      i i I I I           i
    i I   x i
    I I   I I
      I     I i
            x
------------------------------
Dy is sent to hospital
Lehuniru cuts 1 Tree
------------------------------

ท้ายปี

------------------------------
            x

    x

i I     I
    i     I               x
      I
                          i i
      x                   I I
  i I   i I     i       i
  I         i I
            i x i
    I         i I
          o
        x
------------------------------
Fuha cuts 1 Tree
Ka cuts 1 Tree
Ky is sent to hospital
End of the year
11 lumbers VS 4 Lumberjacks
we hire 2 new Lumberjacks
a new Lumberjack enters the forest: Di
a new Lumberjack enters the forest: Dy
maul this year: 6
Evykut has been captured
------------------------------

จบเกม

------------------------------
          x
  i
        x     x

          x                 x
                  x i     x
    i               I
    I i x x   I i           x
                    x   i   i
      x i i i i I
      i i I   I i I   i
I       i     i i
        i   x   i
            I   i I
    I I   x i   I I         x
------------------------------
Vanabixy cuts 1 Tree
Fasiguvy cuts 1 Tree
------------------------------
----------------------------------------------------------------------
End of the game
----------------------------------------------------------------------
month:4800
number of trees still alive: 36
number of lumberjacks still alive: 15
number of bears still alive: 0
----------------------------------------------------------------------
oldest Tree ever is/was: 129
oldest Lumberjack ever is/was: 308 yo Cejuka
oldest Bear ever is/was: 288 yo Ekyx
----------------------------------------------------------------------
max cut by a Lumberjack: 44 by Cejuka
max lumber by a Lumberjack: 44 by Cejuka
max maul by a Bear: 52 by Ekyx
----------------------------------------------------------------------
max lumber in a year: 84
max maul in a year: 86
----------------------------------------------------------------------
Total of:
bears 211
cut 5054
fired 67
capture 211
lumberjacks 1177
lumbers 5054
maul 1095
trees 5090
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.