ผลต่างระหว่างรุ่นของ "Prg2/arcade1"

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
 
(ไม่แสดง 12 รุ่นระหว่างกลางโดยผู้ใช้คนเดียวกัน)
แถว 40: แถว 40:
 
=== Installing pip/pip3 ===
 
=== Installing pip/pip3 ===
  
pip เป็นโปรแกรมสำหรับติดตั้ง library บน Python ในระบบที่มี python3 พร้อม ๆ กับ python2 เราจะเรียก pip3 เพื่อให้ติดตั้ง library ลงในส่วนของ python3
+
'''pip''' is a library package manager for  Python.    In a system with both Python 3 and Python 2, we would call <tt>pip3</tt> to install libraries into Python 3 packages.
  
ให้ลองเรียก
+
Try to call
  
 
  pip
 
  pip
  
หรือ
+
or
  
 
  pip3
 
  pip3
  
ถ้ามีโปรแกรมแล้วก็ข้ามส่วนติดตั้ง pip ได้เลย
+
If it runs, you can skip the pip installation part.
  
==== 1. ติดตั้ง pip บน Windows ====
+
==== 1. Installing pip on Windows ====
  
pip จะมาพร้อมกับการติดตั้ง python  ให้เลือก option ดังกล่าวด้วย ถ้าเปิด command แล้วเรียก pip ขึ้นก็แสดงว่าได้ติดตั้งเรียบร้อย  ถ้าไม่มี ให้ลองติดตั้ง python3 อีกครั้ง และให้เลือก pip ในหน้า Optional Features ด้วย
+
<tt>pip</tt> comes with python. Don't forget to check an option to include pip. If it's not available on the command line, try to install Python 3 again.
  
* ดูรายละเอียดการติดตั้งที่นี่ [https://pythonhosted.org/arcade/installation_windows.html arcade installation บน windows]
+
* Read instructions here: [https://pythonhosted.org/arcade/installation_windows.html arcade installation บน windows]
  
==== 2. ติดตั้ง pip3 บน Linux ====
+
==== 2. Installing pip3 on Linux ====
  
ให้ติดตั้ง pip3 และโปรแกรมประกอบอื่น ๆ โดยสั่ง
+
Install pip3 with other programs with
  
 
  sudo apt install -y python3-dev python3-pip libjpeg-dev zlib1g-dev
 
  sudo apt install -y python3-dev python3-pip libjpeg-dev zlib1g-dev
  
==== 3. ติดตั้ง pip3 บน Mac ====
+
==== 3. Installing pip3 on Mac ====
  
ถ้าติดตั้ง python3 แล้ว น่าจะมี pip3 มาแล้ว ทดลองเรียกดูใน terminal
+
<tt>pip3</tt> usually comes with Python 3.  Try to call it from the terminal.
  
บน Mac จะต้องติดตั้ง libjpg ด้วย ถ้ายังไม่มี ถ้าคุณมี homebrew อยู่แล้ว ให้สั่ง
+
On Mac, you have to install <tt>libjpg</tt>. If you have <tt>homebrew</tt>, call
  
 
  brew install libjpeg
 
  brew install libjpeg
  
ถ้าไม่ได้ ให้ไปดาวน์โหลดและ install จาก [http://ethan.tira-thompson.com/Mac_OS_X_Ports.html] (เลือก libjpg)
+
If it doesn't work, download and install from [http://ethan.tira-thompson.com/Mac_OS_X_Ports.html](choose libjpg)
  
 
=== Use pip to install arcade ===
 
=== Use pip to install arcade ===
  
ถ้าในการติดตั้ง python เราได้ลง pip มาแล้ว เราจะสามารถติดตั้ง arcade ผ่านทาง pip ได้โดยสั่ง
+
For Ubuntu, install arcade using pip (or pip3) with
  
 
  sudo pip3 install arcade
 
  sudo pip3 install arcade
  
ถ้าเป็น windows ให้สั่ง
+
For Mac, use
 +
 
 +
pip3 install PyObjC arcade
 +
 
 +
For Windows, use
  
 
  pip install arcade
 
  pip install arcade
  
'''หมายเหตุ:''' ถ้าติดตั้งด้วย pip แล้ว pip เอาไปลงกับ Python 3.5 ให้ ให้สั่งแบบนี้แทน (ขอบคุณศิรกร):
+
'''Remarks:''' If you install arcade with <tt>pip</tt>, but <tt>pip</tt> puts it in Python 3.5's packages, use the following command to install:
  
 
  python3.6 -m pip install arcade
 
  python3.6 -m pip install arcade
  
 
=== virtualenv ===
 
=== virtualenv ===
เราติดตั้ง arcade ลงใน library ของระบบเลย ซึ่งทำแบบนี้บ่อย ๆ อาจจะทำให้ library เละและตีกันได้ Python มีระบบสำหรับติดตั้ง library แยกกัน เรียกว่า virtualenv
+
We install <tt>arcade</tt> into the system's library.  If you work intensively with Python, having many packages in the system may causes version clashes, you might want to install library locally using ''virtualenv''.  Try to google it if you want to try.
  
: ''ยังเขียนส่วนนี้ไม่เสร็จ: to do - how to install with virtualenv''
+
: ''Sorry NOT READY: to do - how to install with virtualenv''
  
 
=== Testing your installation ===
 
=== Testing your installation ===
แถว 146: แถว 150:
 
Quick notes:
 
Quick notes:
  
* '''randint''' -- สุ่มเลขระหว่างเลขที่ระบุ ต้อง import random ก่อน
+
* '''randint''' -- Random integers between to specified numbers.  You need to <tt>import random</tt>.
* '''global''' -- ใน Python ฟังก์ชันจะสามารถอ้างถึงตัวแปรโกลบอลได้ถ้าอ่านค่าอย่างเดียว แต่ถ้าต้องการเปลี่ยนแปลงตัวแปรโกลบอลจะต้องประกาศให้ชัดเจนว่าจะมีการใช้ตัวแปรโกลบอลนั้น ในฟังก์ชัน on_draw เราต้องการเปลี่ยนค่าขนาดวงกลมและทิศทางการเปลี่ยนค่า เราจึงต้องประกาศ
+
* '''global''' -- In Python, a function can only read from global variables, by default.  If you want to change the variables, you have to declare it explicitly.  In function <tt>on_draw</tt> we would like to change the circle size, so we have to declare that.  It is '''not''' a good practice to use global variables.
* '''zip''' -- ฟังก์ชัน zip นำลิสต์สองลิสต์มารวมเข้าด้วยกัน เช่น zip([1,2,3],['a','b','c']) ได้ผลลัพธ์เทียบเท่ากับ [(1, 'a'), (2, 'b'), (3, 'c')]  มักใช้เวลาต้องการ for ไปในรายการหลายอันพร้อม ๆ กัน
+
* '''zip''' -- <tt>zip</tt> combines to lists, e.g., <tt>zip([1,2,3],['a','b','c'])</tt> would return <tt>[(1, 'a'), (2, 'b'), (3, 'c')]</tt>. We usually use <tt>zip</tt> when we iterate through two lists in a for loop.
* ฟังก์ชันจาก <tt>arcade</tt>
+
* Functions from <tt>arcade</tt>
** arcade.start_render ให้เรียกก่อนจะวาดหน้าจอ จะลบหน้าจอเดิม (มีทดลองในส่วนถัดไป)
+
** arcade.start_render -- Call this before you want to draw a screen.  It will clear the screen.  (Some experiment below.)
** arcade.draw_circle_outline วาดวงกลม
+
** arcade.draw_circle_outline -- Draw a circle
 
** arcade.open_window
 
** arcade.open_window
 
** arcade.set_background_color
 
** arcade.set_background_color
แถว 158: แถว 162:
  
 
== A moving circle ==
 
== A moving circle ==
=== วาดวงกลม ===
+
=== Drawing a circle ===
เราจะเริ่มเขียนจากโปรแกรมที่ทำอะไรไม่ค่อยได้ ไปหาโปรแกรมที่ซับซ้อนขึ้น โปรแกรมด้านล่างวาดรูปวงกลมที่กลางหน้าจอ เขียนและเซฟไว้ในไฟล์ชื่อ <tt>cir1.py</tt>
+
We will start from a very simple program and will move on to more functioning programs. The code below draws a circle at the center of the screen. Take the code and save it as <tt>cir1.py</tt>.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 187: แถว 191:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
'''ทดลอง:''' ให้ทดลองลบบรรทัด <tt>arcade.start_render()</tt> ออก ผลเป็นอย่างไร?
+
Try to run it.  What do you see?
 +
 
 +
'''Experiment:''' Try to remove the line <tt>arcade.start_render()</tt>.  What do you observe?
  
=== เคลื่อนที่ ===
+
=== Move ===
  
เราจะบังคับให้วงกลมเคลื่อนที่ โดยปรับค่าตำแหน่งตามความเร็วสองแกนคือ vx แทนความเร็วในแนวแกน x และ vy แทนความเร็วในแกน y
+
We will move the circle by having velocities <tt>vx</tt> (for the x-axis velocity) and <tt>vy</tt> (for the y-axis velocity).
  
เนื่องจากเราต้องการเปลี่ยนค่าตำแหน่งวงกลมอย่างต่อเนื่อง เราจึงต้องมีตัวแปรโกลบอลเก็บค่าตำแหน่ง คือ x และ y ในฟังก์ชัน on_draw เราจะระบุว่าจะแก้ค่าตัวแปรดังกล่าวด้วย keyword '''global''' เราใส่ vx และ vy เผื่อไว้ด้วยเลย (เพราะว่าเราต้องการให้สะท้อนขอบจอ)
+
Because we would like to keep track of the circle location, we will need global variables <tt>x</tt> and <tt>y</tt> to keep the circle co-ordinate.  We will use global variables for that and in function <tt>on_draw</tt> we will use keyword '''global''' to explicitly tell Python that we will modify these variables.  We will also declare that we will modify <tt>vx</tt> and <tt>vy</tt> (because later on we will let the circle bounce at the edges of the screen.)
  
โค้ดของ on_draw เป็นดังนี้ บรรทัดที่ประกาศ x,y,vx,vy เป็นการกำหนดค่าให้กับตัวแปรโกลบอล
+
The updated code for <tt>on_draw</tt> is shown below.  The lines that define <tt>x,y,vx,vy</tt> assign values to global variables.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 214: แถว 220:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== แบบฝึกหัด 1: ชนและสะท้อนที่ขอบ ===
+
=== Exercise 1: Bounce ===
  
ตอนนี้ลูกบอลเมื่อวิ่งทะลุขอบจอแล้วจะหายไปเลย ให้เพิ่มการตรวจสอบว่าลูกบอลทะลุจอไปแล้วแล้วปรับให้ลูกบอลเสมือนเด้งกลับมา
+
Currently, when the circle hits the edge of the screen, it will keep moving and will be gone forever.  Add a code that checks this situation and change the direction of the circle so that it looks like the circle bounces with the screen edge.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 227: แถว 233:
 
     y += vy
 
     y += vy
  
     # ตรวจสอบตำแหน่ง ถ้าตกขอบให้เปลี่ยนทิศทาง
+
     # TODO: add the code for checking if the circle location is a the edge of the screen
     # hint: ให้ทำทีละแกน จะง่ายกว่า
+
     # hint: it would be easier to consider each axis separately
 
      
 
      
 
     arcade.draw_circle_outline(x, y, 20, arcade.color.BLACK)
 
     arcade.draw_circle_outline(x, y, 20, arcade.color.BLACK)
แถว 235: แถว 241:
 
== Many circles ==
 
== Many circles ==
  
เราจะแก้โปรแกรมให้มีลูกบอลวิ่งไปมาหลาย ๆ ลูก ให้เซฟโปรแกรมที่เขียนในไฟล์ใหม่ชื่อ <tt>cir2.py</tt>
+
After this point, we will refer to circles as balls interchangeably.
 +
 
 +
We will modify our code so that we have many moving circles. Save the new code for this section in file <tt>cir2.py</tt>.
  
เราจะสุ่มตำแหน่งและความเร็วของลูกบอลด้วยฟังก์ชัน <tt>randint</tt> ดังนั้นให้เพิ่มบรรทัด import ดังด้านล่าง
+
We will random the circle locations and speeds with <tt>randint</tt> function; therefore, you should add the following import statements at the beginning of the code.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 244: แถว 252:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
เราจะเก็บตำแหน่งและความเร็วในแกนต่าง ๆ ของลูกบอลในลิสต์ xs, ys, vxs, vys และจำนวนลูกบอลในตัวแปร n
+
We use lists to keep circles' co-ordinates and speeds in lists <tt>xs, ys, vxs, vys</tt> and the number of circles.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 261: แถว 269:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
เราจะเขียนฟังก์ชันสำหรับจัดการลูกบอลลูกที่ i ในรายการดังด้านล่าง จากนั้น <tt>on_draw</tt> จะเรียกฟังก์ชันนี้  สังเกตว่าเรายังไม่ได้จัดการการชนขอบจอ
+
The following function <tt>draw_and_move_circle</tt> handles the i-th ball in the list.  Function <tt>on_draw</tt> will call this function.  Note that the code below does not include the code for bouncing with screen edges.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 270: แถว 278:
 
     arcade.draw_circle_outline(xs[i], ys[i], 20, arcade.color.BLACK)
 
     arcade.draw_circle_outline(xs[i], ys[i], 20, arcade.color.BLACK)
 
</syntaxhighlight>
 
</syntaxhighlight>
   
+
 
<tt>on_draw</tt> วนลูบเรียกฟังก์ชัน <tt>draw_and_move_circle</tt>
+
Function <tt>on_draw</tt> iteratively calls <tt>draw_and_move_circle</tt>.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 281: แถว 289:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
อย่าลืมเรียก initialize ใน main
+
We are ready to add our main function.  Don't forget to call <tt>initialize</tt> in it.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 295: แถว 303:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== แยกฟังก์ชัน ===
+
Finally, add the code to call the main function.
 +
 
 +
<syntaxhighlight lang="python">
 +
if __name__ == '__main__':
 +
    main()
 +
</syntaxhighlight>
 +
 
 +
Try to run the code to see if it works.
 +
 
 +
'''Quick question:''' What can you do to improve the current code to make it more readable and maintainable?
 +
 
 +
=== Split the function ===
  
ฟังก์ชัน <tt>draw_and_move_circle</tt> เป็นตัวอย่างของฟังก์ชันที่พยายามทำหลายอย่างมากเกินไป เราจะแยกฟังก์ชันออกเป็นสองฟังก์ชัน แล้วเรียกใน <tt>on_draw</tt>
+
Function <tt>draw_and_move_circle</tt> is a good example of a function that tries to do too many things.  We will split it into two functions and calls them in <tt>on_draw</tt>.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 314: แถว 333:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== แบบฝึกหัด 2: ชนและสะท้อนที่ขอบ ===
+
=== Exercise 2: Bounce ===
  
โปรแกรมที่มีลูกบอลหลายลูกที่เราเขียนยังไม่ได้มีการจัดการเรื่องการชนกับขอบ ให้แก้ฟังก์ชัน <tt>move_circle</tt> ให้ตรวจสอบและปรับทิศทางของลูกบอลให้สะท้อนกลับมาด้วย
+
The program with many balls does not deal with screen edges.  Modify function <tt>move_circle</tt> so that it checks the situation and updates the balls speeds so that the balls bounce at the screen edges.
  
 
== The Circle class ==
 
== The Circle class ==
สังเกตว่าในโค้ดชุดก่อน เราจะจัดการกับลูกบอลโดยพิจารณาตัวแปรที่เกี่ยวข้องกัน 4 ตัวคือ x, y, vx, และ vy แทบจะตลอดเวลา เราจะ '''extract''' ตัวแปรที่เกี่ยวข้องกันนี้ออกมาเป็น object ของคลาส Circle
+
Observe that for each ball, we maintain four variables, i.e., <tt>x, y, vx,</tt> and <tt>vy</tt>. We shall '''extract''' these variables into a class '''<tt>Circle</tt>'''
  
ให้ย้ายโค้ดจากส่วนที่แล้วมาเขียนใหม่ในไฟล์ <tt>cir3.py</tt>
+
Move all code into file <tt>cir3.py</tt>.
  
คลาสดังกล่าวเขียนบางส่วนได้ดังนี้  อย่าลืมว่าใน python เวลาเขียนเมท็อดจะมีตัวแปร self (ที่เป็นตัวแปรตัวแรก) แทน object ที่ทำงานด้วย และเมท็อด __init__ จะเป็นเมท็อดพิเศษที่ใช้ในการกำหนดค่าเริ่มต้นให้กับ object
+
Clsss <tt>Circle</tt> can be written as follows.  Note that class methods have additional <tt>self</tt> argument that represents the current object the methods are dealing with. Method <tt>__init__</tt> is a special one that initializes the object.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 337: แถว 356:
 
         self.y += self.vy
 
         self.y += self.vy
  
         # เพิ่มโค้ดส่วนจัดการการชนขอบจอที่นี่ด้วย
+
         # TODO: add the bouncing code here.
  
 
     def draw(self):
 
     def draw(self):
แถว 344: แถว 363:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
แก้ส่วนสร้าง object
+
With class Circle, we update the code that creates the balls.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 359: แถว 378:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
แก้เมท็อด <tt>on_draw</tt>
+
Also, we need to modify <tt>on_draw</tt>.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 367: แถว 386:
 
     for c in circles:
 
     for c in circles:
 
         c.move()
 
         c.move()
         c.draw()
+
         c.draw()  
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== แบบฝึกหัด 3: วงกลมที่มีหลายขนาด ===
+
=== Exercise 3: Circles of many sizes ===
เราจะเพิ่มตัวแปร r ลงใน Circle โดยให้มีค่าเป็น 20 ถ้าไม่มีการระบุตอนสร้าง
+
 
 +
We add variable <tt>r</tt> into class Circle with a default of 20.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 384: แถว 404:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
ให้แก้โค้ดที่เกี่ยวข้องให้สุ่มมาแล้วมีลูกบอลหลายขนาดเคลื่อนที่ในหน้าจอ
+
Modify the rest of the code so that the programs generates balls with different sizes and shows these balls on the screen.
  
 
== A simple ball game ==
 
== A simple ball game ==
เราจะเขียนเกมหลบลูกบอล ให้ copy โค้ดจากส่วนก่อน ๆ ลงในไฟล์ชื่อ <tt>cir4.py</tt> แล้วทำงานต่อที่ไฟล์นี้
+
We will write a simple game. Copy the code from the previous section into file <tt>cir4.py</tt> and work on this new file.
  
เราจะนิยามคลาส Player โดย player จะเป็นวงกลมรัศมี 10 สีน้ำเงิน
+
We define class '''Player'''.  We show on screen as a blue ball with radius 10.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 402: แถว 422:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
สร้าง object ของคลาส Python ไว้บริเวณใกล้ ๆ กับที่ประกาศ circles   (การหาร // คือหารปัดเศษ)
+
Add the code for creating an object of class Player near the code where we declare <tt>circles</tt>. (Operator // is an integer division.)
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 410: แถว 430:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
เพิ่มโค้ดให้แสดง player ใน on_draw
+
Show the player in <tt>on_draw</tt>.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 423: แถว 443:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== ขยับ player ===
+
=== Move the player ===
ขั้นตอนนี้จะยุ่งยากเล็กน้อย เนื่องจากเราจะใช้การอ่านการกดปุ่มแบบอ่านสถานะ ในการเขียนครั้งถัด ๆ ไปจะสะดวกกว่านี้
+
We shall control the player through the keyboard.  This step is technically tedious because we have to read the keyboard statuses. In later tutorials, we will use a simpler method.
  
==== เตรียมการอ่าน keys ====
+
==== Preparing to read keys ====
เราจะใช้ส่วนอ่านการกดปุ่มจากไลบรารี pyglet ดังนั้นต้องไป import ก่อน (ใส่ตรงหัวโปรแกรม)
+
We will use library <tt>pyglet</tt> to read the keyboard statuses.  So you have to import it at the beginning of the code.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 452: แถว 472:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==== ส่งปุ่มให้ player ====
+
==== The player's control ====
  
เพิ่มเมท็อด control ในคลาส Player ที่ตอนนี้ตรวจการกดปุ่มซ้ายอย่างเดียว
+
Add method <tt>control</tt> in class <tt>Player</tt>.  The code currently detects only the LEFT key.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
 
class Player:
 
class Player:
     # ... ละโค้ดเก่าไว้
+
     # ... old code hidden
  
 
     def control(self, keys):
 
     def control(self, keys):
แถว 465: แถว 485:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
จากนั้นเรียก player.control จาก on_draw
+
Function <tt>on_draw</tt> then calls <tt>player.control</tt> to inform the player of the key pressed.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 479: แถว 499:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==== แบบฝึกหัด 4: เพิ่มการอ่านปุ่มขึ้นลงขวาให้ครบ ====
+
==== Exercise 4: all the arrows ====
แก้เมท็อด Player.control ให้ตรวจสอบการเคลื่อนที่ให้ครบทุกทิศทาง
+
Modify <tt>Player.control</tt> so that it detects key pressed for all other directions (e.g., up, down, right).
  
=== แบบฝึกหัด 5: โดนชน ===
+
=== Exercise 5: Collision detection ===
  
เขียนเมท็อด is_hit ที่ตรวจสอบว่า player ชนกับ circle หรือไม่
+
Write method <tt>is_hit</tt> that detects collision between the player and a circle.
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
 
class Player:
 
class Player:
     # ... โค้ดอื่น ๆ ไม่แสดง
+
     # ... other code hidden
  
 
     def is_hit(self, circle):
 
     def is_hit(self, circle):
         # ... ตรวจสอบว่า self ชนกับ circle หรือเปล่า
+
         # ... checks of self hits with circle
         # ทำโดยพิจารณาตำแหน่ง self.x, self.y กับ circle.x และ circle.y กับรัศมี circle.r
+
         # The method should consider self.x, self.y and circle.x and circle.y with radius circle.r
         # อย่าลืมว่า player มีรัศมี 10
+
         # Don't forget that the player has radius of 10
 
         #
 
         #
         # method นี้คืนค่า True หรือ False
+
         # The method should return True or False
 
</syntaxhighlight>
 
</syntaxhighlight>
  
ใน on_draw ให้เรียก is_hit ถ้าจริงให้จบเลย
+
You can use <tt>is_hit</tt> in <tt>on_draw</tt>. If the player is hit, the code stops (with <tt>quit()</tt>).
  
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
แถว 514: แถว 534:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
ถ้าเกมยากไป (เปิดมาตายเลย) ให้ลดจำนวนลูกบอล หรือเปลี่ยนการสุ่มให้ไม่อยู่กลางหน้าจอมากไป
+
If your game is too hard (you get instantly dead), you should reduce the number of balls or try to random balls not too close to the center.
  
=== แบบฝึกหัด 6: จบแบบเนียนๆ ===
+
=== Exercise 6: smoother end ===
  
แทนที่จะ quit เลย ให้แก้โปรแกรมให้จบได้เนียนกว่าเดิม เช่นลูกบอลหยุด หรือ player หายไป หรืออะไรก็ได้
+
Instead of just call <tt>quit()</tt> to quit instantly, you should change the code so that it ends more smoothly, e.g., all balls stop or the player get hidden.
  
== แบบฝึกหัด 100: ชนและเด้ง ==
+
== Exercise 100: Hit and bounce ==
ถ้าทำมาถึงจุดนี้ ลองเขียนให้ลูกบอลเด้งเวลาชนกันเองด้วย
+
If you are at this point, try to modify the game so that the balls bounce when they hit each other.

รุ่นแก้ไขปัจจุบันเมื่อ 02:48, 18 มกราคม 2562

This page is a part of Programming 2 (translated from Oop lab/oop in python)

We will start learning how to use the Arcade library and also get a quick review on OOP concepts.

Installing Arcade

We will use the arcade game library which requires at least Python 3.6.

Python 3.6

Let's make sure that you have a proper version of Python. Let's call python from the command line (or terminal)

python --version

If its version is at least 3.6, you are good to go and you can skill to the next step (pip installation). Otherwise, you have to get Python 3.6.

Installing Python 3.6 on Windows

You can follow this instruction. Don't forget to click "Add Python 3.6 to PATH".

Installing Python 3.6 on Ubuntu (version 16.10 onward)

Call

sudo apt-get update
sudo apt-get install python3.6

You can call python3.6 to start python 3.6.

Installing Python 3.6 on older Ubuntu

sudo add-apt-repository ppa:jonathonf/python-3.6
sudo apt-get update
sudo apt-get install python3.6

Installing Python 3.6 on Mac

Follow this instruction.

Installing pip/pip3

pip is a library package manager for Python. In a system with both Python 3 and Python 2, we would call pip3 to install libraries into Python 3 packages.

Try to call

pip

or

pip3

If it runs, you can skip the pip installation part.

1. Installing pip on Windows

pip comes with python. Don't forget to check an option to include pip. If it's not available on the command line, try to install Python 3 again.

2. Installing pip3 on Linux

Install pip3 with other programs with

sudo apt install -y python3-dev python3-pip libjpeg-dev zlib1g-dev

3. Installing pip3 on Mac

pip3 usually comes with Python 3. Try to call it from the terminal.

On Mac, you have to install libjpg. If you have homebrew, call

brew install libjpeg

If it doesn't work, download and install from [1]. (choose libjpg)

Use pip to install arcade

For Ubuntu, install arcade using pip (or pip3) with

sudo pip3 install arcade

For Mac, use

pip3 install PyObjC arcade

For Windows, use

pip install arcade

Remarks: If you install arcade with pip, but pip puts it in Python 3.5's packages, use the following command to install:

python3.6 -m pip install arcade

virtualenv

We install arcade into the system's library. If you work intensively with Python, having many packages in the system may causes version clashes, you might want to install library locally using virtualenv. Try to google it if you want to try.

Sorry NOT READY: to do - how to install with virtualenv

Testing your installation

Copy the following code into cirtest.py and try to run it.

import arcade
from random import randint

SCREEN_WIDTH = 600
SCREEN_HEIGHT = 600

circle_size = 1
size_direction = 1
circle_xs = []
circle_ys = []
num_circles = 100

def random_locations():
    for i in range(num_circles):
        circle_xs.append(randint(10,SCREEN_WIDTH-10))
        circle_ys.append(randint(10,SCREEN_HEIGHT-10))

def on_draw(delta_time):
    global circle_size, size_direction
    
    circle_size += size_direction
    if circle_size > 50:
        size_direction = -1
    elif circle_size == 1:
        size_direction = 1

    arcade.start_render()

    for x,y in zip(circle_xs, circle_ys):
        arcade.draw_circle_outline(x, y, circle_size, arcade.color.BLACK)

    
def main():
    random_locations()

    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT,
                       "Circles")
    arcade.set_background_color(arcade.color.WHITE)

    arcade.schedule(on_draw, 1 / 80)
    arcade.run()

if __name__ == '__main__':
    main()

Quick notes:

  • randint -- Random integers between to specified numbers. You need to import random.
  • global -- In Python, a function can only read from global variables, by default. If you want to change the variables, you have to declare it explicitly. In function on_draw we would like to change the circle size, so we have to declare that. It is not a good practice to use global variables.
  • zip -- zip combines to lists, e.g., zip([1,2,3],['a','b','c']) would return [(1, 'a'), (2, 'b'), (3, 'c')]. We usually use zip when we iterate through two lists in a for loop.
  • Functions from arcade
    • arcade.start_render -- Call this before you want to draw a screen. It will clear the screen. (Some experiment below.)
    • arcade.draw_circle_outline -- Draw a circle
    • arcade.open_window
    • arcade.set_background_color
    • arcade.schedule
    • arcade.run

A moving circle

Drawing a circle

We will start from a very simple program and will move on to more functioning programs. The code below draws a circle at the center of the screen. Take the code and save it as cir1.py.

import arcade

SCREEN_WIDTH = 600
SCREEN_HEIGHT = 600

def on_draw(delta_time):
    arcade.start_render()
    
    x = 300
    y = 300
    arcade.draw_circle_outline(x, y, 20, arcade.color.BLACK)

    
def main():
    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT,
                       "Circles")
    arcade.set_background_color(arcade.color.WHITE)

    arcade.schedule(on_draw, 1 / 80)
    arcade.run()

if __name__ == '__main__':
    main()

Try to run it. What do you see?

Experiment: Try to remove the line arcade.start_render(). What do you observe?

Move

We will move the circle by having velocities vx (for the x-axis velocity) and vy (for the y-axis velocity).

Because we would like to keep track of the circle location, we will need global variables x and y to keep the circle co-ordinate. We will use global variables for that and in function on_draw we will use keyword global to explicitly tell Python that we will modify these variables. We will also declare that we will modify vx and vy (because later on we will let the circle bounce at the edges of the screen.)

The updated code for on_draw is shown below. The lines that define x,y,vx,vy assign values to global variables.

vx = 2
vy = 1
x = 300
y = 300

def on_draw(delta_time):
    arcade.start_render()
    
    global x, y, vx, vy

    x += vx
    y += vy
    
    arcade.draw_circle_outline(x, y, 20, arcade.color.BLACK)

Exercise 1: Bounce

Currently, when the circle hits the edge of the screen, it will keep moving and will be gone forever. Add a code that checks this situation and change the direction of the circle so that it looks like the circle bounces with the screen edge.

def on_draw(delta_time):
    arcade.start_render()
    
    global x, y, vx, vy

    x += vx
    y += vy

    # TODO: add the code for checking if the circle location is a the edge of the screen
    # hint: it would be easier to consider each axis separately
    
    arcade.draw_circle_outline(x, y, 20, arcade.color.BLACK)

Many circles

After this point, we will refer to circles as balls interchangeably.

We will modify our code so that we have many moving circles. Save the new code for this section in file cir2.py.

We will random the circle locations and speeds with randint function; therefore, you should add the following import statements at the beginning of the code.

import arcade
from random import randint

We use lists to keep circles' co-ordinates and speeds in lists xs, ys, vxs, vys and the number of circles.

vxs = []
vys = []
xs = []
ys = []
n = 10

def initialize():
    for i in range(n):
        xs.append(randint(100, SCREEN_WIDTH-100))
        ys.append(randint(100, SCREEN_HEIGHT-100))
        vxs.append(randint(-5,5))
        vys.append(randint(-5,5))

The following function draw_and_move_circle handles the i-th ball in the list. Function on_draw will call this function. Note that the code below does not include the code for bouncing with screen edges.

def draw_and_move_circle(i):
    xs[i] += vxs[i]
    ys[i] += vys[i]

    arcade.draw_circle_outline(xs[i], ys[i], 20, arcade.color.BLACK)

Function on_draw iteratively calls draw_and_move_circle.

def on_draw(delta_time):
    arcade.start_render()

    for i in range(n):
        draw_and_move_circle(i)

We are ready to add our main function. Don't forget to call initialize in it.

def main():
    initialize()
    
    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT,
                       "Circles")
    arcade.set_background_color(arcade.color.WHITE)

    arcade.schedule(on_draw, 1 / 80)
    arcade.run()

Finally, add the code to call the main function.

if __name__ == '__main__':
    main()

Try to run the code to see if it works.

Quick question: What can you do to improve the current code to make it more readable and maintainable?

Split the function

Function draw_and_move_circle is a good example of a function that tries to do too many things. We will split it into two functions and calls them in on_draw.

def move_circle(i):
    # .... อย่าลืมย้ายโค้ดมา

def draw_circle(i):
    # .... อย่าลืมย้ายโค้ดมา

def on_draw(delta_time):
    arcade.start_render()

    for i in range(n):
        move_circle(i)
        draw_circle(i)

Exercise 2: Bounce

The program with many balls does not deal with screen edges. Modify function move_circle so that it checks the situation and updates the balls speeds so that the balls bounce at the screen edges.

The Circle class

Observe that for each ball, we maintain four variables, i.e., x, y, vx, and vy. We shall extract these variables into a class Circle

Move all code into file cir3.py.

Clsss Circle can be written as follows. Note that class methods have additional self argument that represents the current object the methods are dealing with. Method __init__ is a special one that initializes the object.

class Circle:
    def __init__(self, x, y, vx, vy):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy

    def move(self):
        self.x += self.vx
        self.y += self.vy

        # TODO: add the bouncing code here.

    def draw(self):
        arcade.draw_circle_outline(self.x, self.y,
                                   20, arcade.color.BLACK)

With class Circle, we update the code that creates the balls.

circles = []
n = 10

def initialize():
    for i in range(n):
        circle = Circle(randint(100, SCREEN_WIDTH-100),
                        randint(100, SCREEN_HEIGHT-100),
                        randint(-5,5),
                        randint(-5,5))
        circles.append(circle)

Also, we need to modify on_draw.

def on_draw(delta_time):
    arcade.start_render()

    for c in circles:
        c.move()
        c.draw()

Exercise 3: Circles of many sizes

We add variable r into class Circle with a default of 20.

class Circle:
    def __init__(self, x, y, vx, vy, r=20):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.r = r
    # ...

Modify the rest of the code so that the programs generates balls with different sizes and shows these balls on the screen.

A simple ball game

We will write a simple game. Copy the code from the previous section into file cir4.py and work on this new file.

We define class Player. We show on screen as a blue ball with radius 10.

class Player:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def draw(self):
        arcade.draw_circle_filled(self.x, self.y,
                                  10, arcade.color.BLUE)

Add the code for creating an object of class Player near the code where we declare circles. (Operator // is an integer division.)

circles = []
player = Player(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)
n = 10

Show the player in on_draw.

def on_draw(delta_time):
    arcade.start_render()

    for c in circles:
        c.move()
        c.draw()

    player.draw()

Move the player

We shall control the player through the keyboard. This step is technically tedious because we have to read the keyboard statuses. In later tutorials, we will use a simpler method.

Preparing to read keys

We will use library pyglet to read the keyboard statuses. So you have to import it at the beginning of the code.

from pyglet.window import key

เราจะประกาศตัวแปร keys เพื่อใช้อ่านสถานะการกดปุ่ม ให้ใส่ไว้ก่อน on_draw โดยอาจจะประกาศไว้แถว ๆ ที่เราประกาศตัวแปร circles, players, และ n ก็ได้

# ... โค้ดเก่า
circles = []
player = Player(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)
n = 10

# ประกาศ keys
keys = key.KeyStateHandler()

ในฟังก์ชัน main ไปบอกกับหน้าต่างให้อัพเดทสถานะการกดปุ่มผ่านทาง keys โดยเพิ่มบรรทัด push_handlers ให้ใส่ไว้ก่อน arcade.schedule

    arcade.get_window().push_handlers(keys)

    # .. โค้ดเก่า ...    
    arcade.schedule(on_draw, 1 / 80)

The player's control

Add method control in class Player. The code currently detects only the LEFT key.

class Player:
    # ... old code hidden

    def control(self, keys):
        if keys[key.LEFT]:
            self.x -= 5

Function on_draw then calls player.control to inform the player of the key pressed.

def on_draw(delta_time):
    arcade.start_render()

    for c in circles:
        c.move()
        c.draw()

    player.control(keys)
    player.draw()

Exercise 4: all the arrows

Modify Player.control so that it detects key pressed for all other directions (e.g., up, down, right).

Exercise 5: Collision detection

Write method is_hit that detects collision between the player and a circle.

class Player:
    # ... other code hidden

    def is_hit(self, circle):
        # ... checks of self hits with circle
        # The method should consider self.x, self.y and circle.x and circle.y with radius circle.r
        # Don't forget that the player has radius of 10
        #
        # The method should return True or False

You can use is_hit in on_draw. If the player is hit, the code stops (with quit()).

def on_draw(delta_time):
    arcade.start_render()

    for c in circles:
        c.move()
        c.draw()
        if player.is_hit(c):
            quit()

    player.draw()
    player.control(keys)

If your game is too hard (you get instantly dead), you should reduce the number of balls or try to random balls not too close to the center.

Exercise 6: smoother end

Instead of just call quit() to quit instantly, you should change the code so that it ends more smoothly, e.g., all balls stop or the player get hidden.

Exercise 100: Hit and bounce

If you are at this point, try to modify the game so that the balls bounce when they hit each other.