Lab 5 เขียนส่วนควบคุมตัวรถในเกม CodeRally    เอา pdf ของหน้านี้ได้ที่นี่

เริ่มโดย เอาไฟล์จาก

CodeRally [from http://www.alphaworks.ibm.com/tech/coderally]

หรือโหลดไฟล์จาก ลิ้งค์นี้   unzip jar  ทั้งหมดไปไว้ในplugins folder ของ eclipse

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

รถของเรา (คลาสจะเรียกว่า Rally car) สามารถรับรู้ตำแหน่งของสิ่งต่างๆในสนามแข่งได้ รวมถึงสามารถรับรู้สภาพของรถคันอื่นได้ด้วย รถนั้นชนกันได้ แล้วยังโยนยางสำรอง (เก็บได้ในฉาก) เพื่อทำให้รถคันอื่นเสียได้ด้วย นอกจากนี้ยังเปลี่ยนเป็นรถตำรวจ (เรียกอีกอย่างว่า เข้า protected mode) ซึ่งเป็นอมตะชั่วคราวได้อีกด้วย

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

ในการคิดคะแนน วิธีคิดของคนที่ทำ CodeRally เป็นดังนี้

ซึ่งรถที่ได้คะแนนมากที่สุดจะถือว่าชนะ

เอาล่ะ มาเริ่มเล่นกันดีกว่า

1. ไปที่ File > New > Project

2. เลือก Other > Game Project คลิ้กที่ Next ตามรูป Figure 1.

Figure 1 Create new Game Project

3. เลือกให้เกมเป็น CodeRally ใส่ ID นิสิตลงตรง project name จากนั้นคลิ้ก Finish ตามรูป Figure 2.

Figure 2 New Game Project name

4. โครงสร้างโปรเจ็คจะเป็นดัง Figure 3.

Figure 3 Your game project

5. เลือก RallyCar.java ไฟล์นี้จะเป็นไฟล์ที่เราต้องแก้ไฟล์เดียวเท่านั้น ในเมธอด getColor() ให้เปลี่ยนสีจาก CAR_BLUE เป็นสีใดก็ได้ที่นิยามไว้ใน Car.java (อันนี้ทำเพื่อให้เราได้ใส่สีที่ชอบเฉยๆ ไม่มีอะไรพิเศษ)

6. ลองรันดูเลย วิธีการคือ เลือก games.xml จากนั้นใส่ชื่อและสถาบัน แล้วคลิ้ก Add My Team จากนั้นก็เลือกผู้เล่นที่บังคับด้วยคอมอื่นๆ แล้ว Add ไปด้วย พอเสร็จแล้วให้รันด้วยการคลิ้ก Run ดัง Figure 4.

Figure 4 Setup your car to the race

7. สภาพการแข่งจะเป็นดังรูป Figure 5

8. ถ้าจะเอาไฟล์เอกสารของเมธอดต่างๆ และคลาสต่างๆ,

8.1. คลิ้กชื่อโปรเจ็คแล้วเลือกเมนู File > Export

8.2. เลือก Java > Javadoc

8.3. คลิ้ก Next แล้วก็ Finish. (อาจต้องมีการหา javadoc.exe ให้เจอ ซึ่งปกติจะอยู่ในโฟลเดอร์ของ jdk)

9. เท่านี้ก็จะได้โฟลเดอร์ doc ออกมา ซึ่งเราจะดูคำอธิบายของทุกคลาสได้ (คำอธิบายเมธอดและคลาส เราจะเรียกว่า javadoc)

10. สิ่งที่เราต้องเขียนก็คือ เมธอด move() ใน RallyCar.java

โดยเราต้องเขียนให้

10.1 ให้รถวิ่งตามเช็คพอยต์

10.2 ให้รถหยุดเติมน้ำมันได้ 

10.3 เขียนพฤติกรรมอื่นๆเพิ่มได้ตามใจ แต่ต้องไม่ขัดกับพฤติกรรมสองอย่างข้างต้น เช่น แปลงร่างเป็นรถตำรวจเมื่อชน แล้วไล่ชนคนอื่นจนกว่าจะกลับเป็นรถธรรมดา หรือ แปลงเป็นรถตำรวจ พอชนแล้วถอยเข้าชนอีกเพื่อซ้ำให้ตายจนกว่าจะกลับเป็นรถธรรมดา เป็นต้น

10.4 เขียนคอมเม้นกำกับ เมธอด move ด้วย ว่ารถของเรามีพฤติกรรมอย่างไร 

การตั้งชื่อ project และการตั้งชื่อไฟล์ รวมถึงการขึ้นต้นไฟล์ ใช้เกณฑ์เดียวกับแล็บที่แล้ว 

ไฟล์ที่ส่ง ให้เป็น jar file ที่มีซอร์สโค้ดอยู่ภายใน ตั้งชื่อด้วยเกณฑ์เดียวกับแล็บที่แล้ว

subject ของอีเมล์ที่ส่ง ให้ใช้เกณฑ์ของแล็บที่แล้วเช่นเดียวกัน

กำหนดส่งคือ วันอาทิตย์ ภายในเวลา 0.00 (เวลาเที่ยงคืนของคืนวันเสาร์)  

ขอบอกว่า อย่าประมาท ให้อ่านเดี๋ยวนี้ เพราะมีเนื้อหาให้อ่านเยอะ ไม่งั้นกว่าจะได้เริ่มจะทำไม่ทัน

ต่อไปนี้เป็นคำแนะนำนะ อ่านไว้จะได้ช่วยในการเขียนโค้ด

Coding Your Car – Overview

RallyCar.java นั้นมีโครงอยู่แล้ว เราสามารถเติมตัวแปรและเมธอดเพิ่มเติมในคลาสนี้ได้ตามใจ สร้างคลาสอื่นก็ยังได้

พอต้องการจะทดสอบ อย่าลืมเซฟซะก่อน แล้วเลือก games.xml แล้วรัน

คลาส Car.java เป็นซุปเปอร์คลาสของ RallyCar จะมีเมธอดหลายๆอย่างที่ RallyCar รับสืบทอดไป จงใช้เมธอดเหล่านี้ให้เป็นประโยชน์

ห้ามแก้คลาส Car จริงๆแล้ว เวลารัน CodeRally รันคนละ Car กับที่เรามี ที่เราได้มาเป็นแค่ตัวโครงเฉยๆ

นอกจากนี้ยังมี อินเตอร์เฟสอีกสามอัน (โปรดไปอ่านเรื่อง Interface และ abstract class เพื่อความเข้าใจตรงนี้ จะช่วยได้มาก)

IObject.java

เป็นอินเตอร์เฟสของทุกออบเจ็กต์ในสนามแข่ง ทุกออบเจ็กต์จะ implement อินเตอร์เฟสนี้ (ไปอ่านว่า การ implement คืออะไร) อินเตอร์เฟสนี้นิยามเมธอด

getX() และ getY() ซึ่งรีเทิร์นตำแหน่งของออบเจ็กต์ในสนามแข่ง ซึ่งเลขของ x กับ y นั้นไม่มีค่าลบ และเป็นไทป์ double

ISpareTire.java

อินเตอร์เฟสนี้ extend มาจาก IObject ซึ่งอินเตอร์เฟสนี้นิยามยางสำรองที่อยู่ในสนามแข่ง ยางทุกเส้นจะ implement อินเตอร์เฟสนี้ อินเตอร์เฟสนี้มีเมธอด getHeading() และ getSpeed() ดังนั้นเราสามารถรู้ข้อมูลของยางระหว่างที่ถูกขว้างไปได้

ICar.java

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

The CodeRally Track Simulation

Identification

มีโครงเมธอดใน RallyCar เพื่อไว้สำหรับให้รถเราดูต่างจากคันอื่น เมธอดนี้ก็คือ getColor() ซึ่งต้องรีเทิร์น byte constant ที่เลือกมาจากสีรถที่นิยามไว้ในคลาส Car เราใช้เมธอดนี้ในการให้สีรถของเรา  ค่า default ของ getColor() นั้นคือ CAR_BLUE

เมธอดนี้ ต้องไม่ทำอะไรนอกจากรีเทิร์นค่าคงที่

Initialization

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

Moving Your Car

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

move() นั้นมีพารามิเตอร์สี่ตัว ซึ่งเป็นการบอกค่าต่างๆจาก cycle การทำงานรอบที่แล้ว ซึ่งพารามิเตอร์เหล่านี้บอกถึง:

  • การใช้เวลาของเมธอด move() (หน่วยเป็น milliseconds) ในรอบการทำงานที่แล้ว

  • รอบที่แล้วชนขอบสนามหรือเปล่า

  • รอบที่แล้ว เราชนกับรถคันอื่นหรือเปล่า

  • รอบที่แล้ว เราโดนยางขว้างโดนหรือเปล่า  

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

    CodeRally Track Details

    สนามแข่งนั้นเป็นสองมิติ แกน x ยาว 1010 หน่วย ส่วนแกน y ยาว 580 หน่วย จุด (0,0) อยู่ซ้ายบนของจอ สนามมีผนังล้อมรอบ ซึ่งรถออกนอกผนังนี้ไม่ได้ ไม่มีผนังอยู่ข้างในสนามแต่อย่างใด รถสามารถเคลื่อนที่ไปไหนก็ได้ในสนาม ยกเว้นแต่ว่าชน หรือ ติดรถคันอื่น ออบเจ็กต์เคลื่อนที่ในทิศทางที่เรียกว่า headings ซึ่งวัดเป็นจำนวนเต็ม หน่วยเป็นองศา (0 องศา หมายถึง ตรงขึ้นข้างบน) องศานั้นมีค่าตั้งแต่ 0 ถึง 359  ค่าขององศาเพิ่มตามเข็มนาฬิกา

    Figure 5 Simulated track world

    ในโลกของเกมนั้นมีสิ่งต่างๆดังต่อไปนี้:

    มีนาฬิกา ticking clock ซึ่งค่าของมันอ่านได้ด้วย getClockTicks()

    ในตอนเริ่มแข่งแต่ละนัด รถแต่ละคันจะมีน้ำมัน 100 หน่วยและยางเอาไว้ขว้าง 3 เส้น

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

    พวงมาลัยและคันเร่งเปลี่ยนค่าได้ทันที แต่ว่าความเร็วและทิศทางจะไม่เปลี่ยนในทันทีเพราะรถนั้นมีความเฉื่อย

    ค่าคันเร่งน้อยสุด (MIN_THROTTLE) คือ -50 และค่าคันเร่งมากสุด(MAX_THROTTLE) คือ 100 อัตราการเปลี่ยนความเร็ว(นับจากสภาพรถหยุด) คือ 8 หน่วยต่อ tick (ยกเว้นกรณีที่เกิดการชนกัน)

    ค่าหักพวงมาลัยน้อยที่สุด (MAX_STEER_LEFT) คือ -10 และค่าหักพวงมาลัยมากสุด (MAX_STEER_RIGHT) คือ 10 อัตราการเปลี่ยนทิศนั้นขึ้นกับความเร็วของรถ เราสามารถหาอัตรานี้ได้โดยใช้ getChangeInHeading()

    ตำแหน่งของรถ บนสนามแข่งนั้น เป็นจุด รถมีความยาว 60 หน่วย และกว้าง 40 หน่วย โดยตำแหน่งคือจุดกลางรถนั้นเอง

    เราถือว่า ยางที่ขว้างออกไป ชนรถ เมื่อตำแหน่งของมันอยู่ใกล้จุดกลางรถ 40 หน่วย หรือน้อยกว่า

    ยางที่ขว้างนั้น มีความเร็วคงที่ เท่ากับ 12 หน่วย/tick เมื่อชนกับรถหรือผนังแล้ว ยางจะหายไป

    จุดเช็คพอยต์ ที่เติมน้ำมัน และที่เติมยาง ไม่มีผลต่อยางที่ขว้างออกมา ยางที่ขว้างออกมาจะไม่มีวันชนกัน

    รถมีน้ำมันได้มากที่สุด 100 หน่วย

    รถมียางไว้ขว้างได้มากสุด 5 เส้น

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

    เมื่อรถอยู่ใกล้ที่เติมยางเป็นระยะ  25 หน่วยหรือใกล้กว่า (และรถมียางสำหรับขว้างน้อยกว่า 5 เส้น) รถจะเก็บยางด้วยอัตรา 1 เส้นต่อ 5 clock ticks ในสนามแข่งจะมีที่เติมยางอยู่ 3 ที่ ตำแหน่งของที่เติมยางจะเป็นการสุ่ม

    รถสามารถป้องกันตัวเองได้ด้วยการเข้า "protect mode" แปลงเป็นรถตำรวจ ซึ่งรถที่เข้าโหมดนี้นั้น จะกินน้ำมันสองเท่าของทั่วไป โหมดนี้นั้นจะคงอยู่ 50 clock tick

    ถ้ารถเราชนกับรถอีกคัน โมเม้นตั้มจะถ่ายถึงกัน รถที่ชนจะเสียน้ำมัน 10 หน่วย (ยกเว้นถ้ารถนั้นอยู่ใน protect mode)

    ถ้าเรายิงยางชนคันอื่น เราจะได้ 10 คะแนน ส่วนรถที่โดนยางจะน้ำมันลด 10 หน่วย เมธอด move() จะไม่ถูกเรียกไป 10 clock tick และรถจะถูกแรงยางพาให้เคลื่อนไปนิดหน่อย เราจะไม่ได้คะแนนถ้ารถที่โดนเราขว้างอยู่ใน protect mode รถที่อยู่ใน protect mode ก็จะไม่เป็นไร ไม่โดนแรงยางด้วย

    เมื่อขว้างยางไปแล้ว รถเราจะไม่สามารถโยนยางได้อีก 25 clock tick

    move() หนึ่งนั้น จำกัดเวลาได้แค่ 500 มิลลิวินาที ถ้าในช่วงเวลานี้ยังทำ move() ไม่เสร็จ เมธอด move() จะไม่ถูกเรียกอีกต่อไปในการแข่งนั้น รถคันนั้นจะคงค่าต่างๆที่ตั้งไว้ล่าสุด  

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

    ตารางคะแนนเป็นดังนี้ คะแนนในการแข่งนั้นคิดจากคะแนนรวม

    Action

    Points Earned

    ผ่านเช็คพอยต์

    2

    ผ่านเช็คพอยต์เรียงลำดับเบอร์

    6

    ยิงยางโดนรถคันอื่น

    10

    สำหรับทุกๆ 10 หน่วยของน้ำมันที่เหลือ หลังจากแข่งเสร็จแล้ว

    1

    General Information, Caveats, Constraints, and Restrictions

    Car ไม่สามารถมี constructor ได้

    Car ห้ามใช้ static initialization block ในการ initialize

    โค้ดทั้งหมดต้องอยู่ในคลาส RallyCar

    Car ห้ามสร้าง thread เอง ห้ามปริ๊น ห้ามสร้างไฟล์ ห้ามทำ system function

    Car ใช้ System.out.println() เพื่อพิมพ์ลง Eclipse console ได้ แต่ว่าเวลาที่ใช้ก็นับเป็นส่วนหนึ่งของเวลา move() ด้วย อย่าลืมว่าฟังก์ชั่นพวกนี้ใช้เวลานาน

    ห้ามสร้างโค้ดที่จะทำลายระบบการแข่ง

     

    Example RallyCar Code

    ข้างล่างเป็นตัวอย่างโค้ดของ move() เป็นโค้ดจากหลายๆส่วนนะ ไม่ใช่ move() อันเดียว โปรดสังเกตว่าโค้ดเหล่านี้เป็นตัวอย่างเท่านั้น เราสามารถเขียนโค้ดให้ดีกว่านี้อีกมากมายนัก

    /**

    * Go toward the first spare tire depot.

    */

    public void move(int lastMoveTime, boolean hitWall,

         ICar collidedWithCar, ICar hitBySpareTire) {

         // pick a spare tire depot

         IObject st = getSpareTireDepot()[0];

         // go toward the checkpoint

         int h = getHeadingTo(st);

         if (getHeading() > h)

              setSteeringSetting(MAX_STEER_LEFT);

         else

              setSteeringSetting(MAX_STEER_RIGHT);

         setThrottle(MAX_THROTTLE);

    }

    /**

    * Put the car in reverse for a few moves if you collide with another car.

    */

    protected int wait;

    public void move(int lastMoveTime, boolean hitWall,

         ICar collidedWithCar, ICar hitBySpareTire) {

         if (collidedWithCar != null)

              wait = 10;

         if (wait > 0)

              setThrottle(MIN_THROTTLE);

         else

              setThrottle(MAX_THROTTLE);

         if (wait > 0)

              wait--;

    }