สร้างเกม เป่ายิ้งฉุบ! โดย Python
Published: 2023-04-07 13:48:05

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

หากไม่คุ้นเคย เป่ายิ้งฉุบเป็นเกมมือสำหรับผู้เล่นสองคนขึ้นไป ผู้เล่นพูดว่า "เป่ายิ้งฉุบ"  

1. ค้อน   >> กำมือ
2.กระดาษ >> เเบมือ
3.กรรไกร  >> ชูสองนิ้ 

กฎมีดังนี้:
- กรรไกรทุบหิน
- กระดาษครอบหิน
- กรรไกรตัดกระดาษ

เมื่อทราบกฏเเล้ว จึงเริ่มคิดว่ากฎเหล่านี้จะแปลเป็น Python ได้อย่างไร

**เล่นเกมเป่ายิ้งฉุบเกมเดียวใน Python**
คุณสามารถสร้างเกมเป่ายิ้งฉุบได้โดยใช้คำอธิบายและกฎด้านบน ก่อนที่จะเริ่มเล่นเกม คุณจะต้องนำเข้าโมดูลที่คุณจะใช้เพื่อจำลองการเล่นของคอมพิวเตอร์:
 
```python
import random
```
**รับอินพุตของผู้ใช้**
การรับข้อมูลจากผู้ใช้นั้นค่อนข้างตรงไปตรงมาใน Python เป้าหมายในที่นี้คือการถามผู้เล่นว่าต้องการเลือกอะไร(ค้อน กรรไกร กระดาษ) จากนั้นกำหนดตัวเลือกนั้นให้กับตัวแปร:

```python
user_action = input("Enter a choice (rock, paper, scissors): ")
```
ผลลัพธ์ :
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ru7na3otmbyj2lcmtlit.JPG)
จำลองการเล่น :

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ksuhn3g2aqs86f3xltms.JPG)
จากรูปที่เเสดงจะแจ้งให้ผู้เล่นป้อนการเลือกและบันทึกลงในตัวแปรเพื่อใช้ในภายหลัง เมื่อผู้เล่นเลือกเเล้ว ต่อไปคอมพิวเตอร์จะประมวลว่าควรเลือกอะไร (ค้อน กรรไกร กระดาษ)

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

ใช้คำสั่ง Random.choice() เพื่อให้คอมพิวเตอร์สุ่ม:

```python
possible_actions = ["rock", "paper", "scissors"]
computer_action = random.choice(possible_actions)
```
คำสั่งนี้ทำให้สามารถเลือกองค์ประกอบแบบสุ่มได้จากการที่ ผู้เล่นสามารถพิมพ์ตัวเลือกได้:

```python
print(f"\nYou chose {user_action}, computer chose {computer_action}.\n")
```
ผลลัพธ์ :

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4kxx9pgc1ldwuc9i1a58.JPG)

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

**การกำหนดผู้ชนะ**
ตอนนี้ผู้เล่นทั้งสองได้เลือกแล้ว แค่ต้องการวิธีตัดสินว่าใครจะชนะ 
โดยการใช้บล็อก if … elif … else สามารถเปรียบเทียบตัวเลือกของผู้เล่นและตัดสินผู้ชนะได้ โดยใช้คำสั่งดังต่อไปนี้:
```python
if user_action == computer_action:
    print(f"Both players selected {user_action}. It's a tie!")
elif user_action == "rock":
    if computer_action == "scissors":
        print("Rock smashes scissors! You win!")
    else:
        print("Paper covers rock! You lose.")
elif user_action == "paper":
    if computer_action == "rock":
        print("Paper covers rock! You win!")
    else:
        print("Scissors cuts paper! You lose.")
elif user_action == "scissors":
    if computer_action == "paper":
        print("Scissors cuts paper! You win!")
    else:
        print("Rock smashes scissors! You lose.")
```
ผลลัพธ์ :

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gprvclzigjsr966enwqj.JPG)

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

**เมื่อเขียนโค้ดรวมกันทั้งหมดจะเเสดงได้ดังนี้ :
**
```python
import random

user_action = input("Enter a choice (rock, paper, scissors): ")
possible_actions = ["rock", "paper", "scissors"]
computer_action = random.choice(possible_actions)
print(f"\nYou chose {user_action}, computer chose {computer_action}.\n")

if user_action == computer_action:
    print(f"Both players selected {user_action}. It's a tie!")
elif user_action == "rock":
    if computer_action == "scissors":
        print("Rock smashes scissors! You win!")
    else:
        print("Paper covers rock! You lose.")
elif user_action == "paper":
    if computer_action == "rock":
        print("Paper covers rock! You win!")
    else:
        print("Scissors cuts paper! You lose.")
elif user_action == "scissors":
    if computer_action == "paper":
        print("Scissors cuts paper! You win!")
    else:
        print("Rock smashes scissors! You lose.")
```

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

**เล่นหลายเกมติดต่อกัน**
แม้ว่าเกมเป่ายิ้งฉุบเกมเดียวจะสนุก แต่จะดีกว่าไหมถ้าสามารถเล่นหลาย ๆ เกมติดต่อกันได้ การเล่นซ้ำเป็นวิธีที่ดีที่สุด โดยเฉพาะอย่างยิ่ง คุณสามารถใช้การวนซ้ำแบบ while เพื่อเล่นแบบไม่มีกำหนด:

```python
import random

while True:
    user_action = input("Enter a choice (rock, paper, scissors): ")
    possible_actions = ["rock", "paper", "scissors"]
    computer_action = random.choice(possible_actions)
    print(f"\nYou chose {user_action}, computer chose {computer_action}.\n")

    if user_action == computer_action:
        print(f"Both players selected {user_action}. It's a tie!")
    elif user_action == "rock":
        if computer_action == "scissors":
            print("Rock smashes scissors! You win!")
        else:
            print("Paper covers rock! You lose.")
    elif user_action == "paper":
        if computer_action == "rock":
            print("Paper covers rock! You win!")
        else:
            print("Scissors cuts paper! You lose.")
    elif user_action == "scissors":
        if computer_action == "paper":
            print("Scissors cuts paper! You win!")
        else:
            print("Rock smashes scissors! You lose.")
```
**Highlighted**
    `play_again = input("Play again? (y/n): ")
    if play_again.lower() != "y":
        break`
ผลลัพธ์


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j0be1j8wgv2kkv3gc56o.JPG)

สังเกตโค้ดที่ไฮไลต์ด้านบน สิ่งสำคัญคือต้องตรวจสอบว่าผู้ใช้ต้องการเล่นอีกครั้งหรือไม่และหยุดเล่นหากไม่ต้องการ หากไม่มีการตรวจสอบ ผู้เล่นจะถูกบังคับให้เล่นจนกว่าจะยุติคอนโซลโดยใช้ Ctrl+C 

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

**นี่คือผลลัพธ์ของการเปรียบเทียบสตริงที่แตกต่างกัน:**

```
>>> play_again = "yes"
>>> play_again == "n"
False
>>> play_again != "y"
True
```
เเต่นั่นไม่ใช่สิ่งที่ผู้เล่นต้องการ ผู้เล่นอาจจะไม่พอใจหากป้อน "yes" โดยคาดว่าจะเล่นอีกครั้งแต่ถูกเตะออกจากเกม

**อธิบายการดำเนินการด้วย enum.IntEnum**
เนื่องจากการเปรียบเทียบสตริงอาจทำให้เกิดปัญหาอย่างที่เห็นด้านบน จึงควรหลีกเลี่ยงหากทำได้ อย่างไรก็ตาม สิ่งแรกที่โปรแกรมของคุณถามคือให้ผู้ใช้ป้อนสตริง! จะเกิดอะไรขึ้นหากผู้ใช้ป้อน "Rock" หรือ "rOck" โดยไม่ได้ตั้งใจ การใช้อักษรตัวพิมพ์ใหญ่มีความสำคัญ ดังนั้นจึงไม่เท่ากัน:
```python
>>> print("rock" == "Rock")
False
```
เนื่องจากการใช้อักษรตัวพิมพ์ใหญ่มีความสำคัญ "r" และ "R" จึงไม่เท่ากัน วิธีหนึ่งที่เป็นไปได้คือใช้ตัวเลขแทน การกำหนดตัวเลขให้แต่ละการกระทำสามารถช่วยคุณแก้ปัญหาได้:

```python
ROCK_ACTION = 0
PAPER_ACTION = 1
SCISSORS_ACTION = 2
```
สิ่งนี้ทำให้คุณสามารถอ้างอิงการกระทำต่างๆ ตามหมายเลขที่กำหนด จำนวนเต็มจะไม่เจอปัญหาการเปรียบเทียบเช่นเดียวกับสตริง ดังนั้นวิธีนี้จึงใช้ได้ ตอนนี้สามารถให้ผู้เล่นป้อนตัวเลขและเปรียบเทียบกับค่าได้โดยตรง:

```python
user_input = input("Enter a choice (rock[0], paper[1], scissors[2]): ")
user_action = int(user_input)
if user_action == ROCK_ACTION:
    # Handle ROCK_ACTION
```
เนื่องจาก input() ส่งคืนสตริง ต้องแปลงค่าที่ส่งคืนเป็นจำนวนเต็มโดยใช้ int() จากนั้นสามารถเปรียบเทียบอินพุตกับแต่ละค่าด้านบน วิธีนี้ใช้ได้ดี แต่อาจต้องพึ่งการตั้งชื่อตัวแปรอย่างถูกต้องเพื่อติดตามตัวแปรทั้งหมด วิธีที่ดีกว่าคือการใช้ enum.IntEnum และกำหนดคลาสได้

การใช้ enum.IntEnum ช่วยให้สร้างแอตทริบิวต์และกำหนดค่าให้คล้ายกับที่แสดงด้านบน สิ่งนี้จะช่วยล้างโค้ดของคุณโดยจัดกลุ่มลงในเนมสเปซของตนเอง :

```python
from enum import IntEnum

class Action(IntEnum):
    Rock = 0
    Paper = 1
    Scissors = 2
```
การดำเนินการนี้จะสร้างการดำเนินการแบบกำหนดเองที่สามารถใช้เพื่ออ้างอิงการดำเนินการประเภทต่างๆ ที่สนับสนุนการทำงานโดยกำหนดแต่ละแอตทริบิวต์ภายในให้เป็นค่าที่คุณระบุ

การเปรียบเทียบยังคงตรงไปตรงมาเเละมีชื่อคลาสที่เป็นประโยชน์ที่เกี่ยวข้อง:

```python
>>> Action.Rock == Action.Rock
True
```
เนื่องจากค่าสมาชิกเท่ากัน การเปรียบเทียบจึงเท่ากัน ชื่อคลาสยังทำให้ชัดเจนขึ้นว่าต้องการเปรียบเทียบสองการกระทำ
```
Note: To learn more about enum, check out the Build Enumerations of Constants With Python’s Enum.
```
สามารถสร้าง Action จาก int:

```
>>> Action.Rock == Action(0)
True
>>> Action(0)
<Action.Rock: 0>
```
การดำเนินการจะพิจารณาค่าที่ส่งผ่านและส่งคืนการดำเนินการที่เหมาะสม สิ่งนี้มีประโยชน์เพราะสามารถรับอินพุตของผู้เล่นเป็น int และสร้างการดำเนินการจากมันได้ ไม่ต้องกังวลกับการสะกดอีกต่อไป

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

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

```python
import random
from enum import IntEnum

class Action(IntEnum):
    Rock = 0
    Paper = 1
    Scissors = 2
```
ต่อไปนี้เป็นโค้ดสำหรับ get_user_selection() ซึ่งไม่ใช้อาร์กิวเมนต์ใดๆ และคืนค่า Action:

```python
def get_user_selection():
    user_input = input("Enter a choice (rock[0], paper[1], scissors[2]): ")
    selection = int(user_input)
    action = Action(selection)
    return action
```
สังเกตว่าใช้อินพุตของผู้เล่นเป็น int และรับกลับเป็น Action อย่างไร ข้อความที่ยาวสำหรับผู้เล่นนั้นค่อนข้างยุ่งยาก จะเกิดอะไรขึ้นถ้าต้องการเพิ่มการดำเนินการอื่นๆ ต้องเพิ่มข้อความเพิ่มเติมในพรอมต์

สามารถใช้รายการความเข้าใจเพื่อสร้างส่วนของอินพุตแทน:

```python
def get_user_selection():
    choices = [f"{action.name}[{action.value}]" for action in Action]
    choices_str = ", ".join(choices)
    selection = int(input(f"Enter a choice ({choices_str}): "))
    action = Action(selection)
    return action
```
การทดสอบนี้ จะเห็นวิธีที่โค้ดแจ้งผู้ใช้และส่งคืนการดำเนินการที่เกี่ยวข้องกับค่าอินพุตของผู้เล่น:

```python
>>> get_user_selection()
Enter a choice (rock[0], paper[1], scissors[2]): 0
<Action.Rock: 0>
```
ตอนนี้ต้องการฟังก์ชันสำหรับรับคำสั่งของคอมพิวเตอร์ เช่นเดียวกับ get_user_selection() ฟังก์ชันนี้ไม่ควรใช้อาร์กิวเมนต์และส่งคืนการดำเนินการ เนื่องจากค่าสำหรับ Action มีตั้งแต่ 0 ถึง 2 คุณจึงต้องการสร้างตัวเลขสุ่มภายในช่วงนั้น Random.randint() สามารถช่วยได้

Random.randint() ส่งกลับค่าสุ่มระหว่างค่าต่ำสุดและค่าสูงสุดที่ระบุ (รวม)สามารถใช้ len() เพื่อช่วยหาขอบเขตบนที่ควรจะเป็นในโค้ด:

```python
def get_computer_selection():
    selection = random.randint(0, len(Action) - 1)
    action = Action(selection)
    return action
```
เนื่องจากค่า Action เริ่มนับจาก 0 และ len() เริ่มนับจาก 1 จึงเป็นสิ่งสำคัญที่ต้องทำ len(Action) - 1

เมื่อทดสอบสิ่งนี้ จะไม่มีการแจ้ง มันจะส่งคืนการกระทำที่เกี่ยวข้องกับตัวเลขสุ่ม:

```python
>>> get_computer_selection()
<Action.Scissors: 2>
```
ต่อไป คุณต้องหาวิธีตัดสินผู้ชนะ ฟังก์ชันนี้จะรับอาร์กิวเมนต์สองรายการ ได้แก่ การกระทำของผู้ใช้และการกระทำของคอมพิวเตอร์ ไม่จำเป็นต้องส่งคืนสิ่งใดเนื่องจากจะแสดงผลไปยังคอนโซล:

```python
def determine_winner(user_action, computer_action):
    if user_action == computer_action:
        print(f"Both players selected {user_action.name}. It's a tie!")
    elif user_action == Action.Rock:
        if computer_action == Action.Scissors:
            print("Rock smashes scissors! You win!")
        else:
            print("Paper covers rock! You lose.")
    elif user_action == Action.Paper:
        if computer_action == Action.Rock:
            print("Paper covers rock! You win!")
        else:
            print("Scissors cuts paper! You lose.")
    elif user_action == Action.Scissors:
        if computer_action == Action.Paper:
            print("Scissors cuts paper! You win!")
        else:
            print("Rock smashes scissors! You lose.")

```
สิ่งนี้ค่อนข้างคล้ายกับการเปรียบเทียบครั้งแรกที่ใช้ในการตัดสินผู้ชนะ ตอนนี้สามารถเปรียบเทียบประเภทการกระทำได้โดยตรงโดยไม่ต้องกังวลกับสตริง

ยังสามารถทดสอบได้โดยส่งตัวเลือกต่างๆ เพื่อตรวจสอบว่าจะพิมพ์ออกมาอย่างไร:

```python
>>> determine_winner(Action.Rock, Action.Scissors)
Rock smashes scissors! You win!
```
เนื่องจากกำลังสร้างการดำเนินการจากตัวเลข จะเกิดอะไรขึ้นหากผู้ใช้ของคุณพยายามสร้างการดำเนินการจาก 3 โปรดจำไว้ว่าจำนวนสูงสุดที่คุณกำหนดไว้คือ 2:

```python
>>> Action(3)
ValueError: 3 is not a valid Action
```
อ๊ะ! คุณไม่ต้องการให้เกิดขึ้น สามารถเพิ่มตรรกะที่ใดในผังงานเพื่อให้แน่ใจว่าผู้เล่นป้อนตัวเลือกที่ถูกต้อง

ควรรวมเช็คทันทีหลังจากที่ผู้เล่นเลือก:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xpyokclhsd64v0kcfb39.JPG)

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

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

```python
while True:
    try:
        user_action = get_user_selection()
    except ValueError as e:
        range_str = f"[0, {len(Action) - 1}]"
        print(f"Invalid selection. Enter a value in range {range_str}")
        continue

    computer_action = get_computer_selection()
    determine_winner(user_action, computer_action)

    play_again = input("Play again? (y/n): ")
    if play_again.lower() != "y":
        break
```
สังเกตว่าหากผู้เล่นไม่สามารถเลือกช่วงที่ถูกต้องได้ จะใช้การดำเนินการต่อแทนการแบ่ง สิ่งนี้ทำให้รหัสดำเนินการต่อไปยังการวนซ้ำถัดไปแทนที่จะแยกออกจากกัน

**สรุปได้ว่า**
  การสร้างเกม เป่ายิ้งฉุบ โดยใช้การรับอินพุตจากผู้เล่น เขียนโค้ดเพื่อให้คอมพิวเตอร์สุ่มค่าในเกมเพื่อเเทนผู้เล่นอีกคน ในการกำหนดผู้ชนะจะใช้เงื่อนไข  if … elif … else มาตัดสินผู้ชนะในเกม ส่วนในการสร้างเงื่อนไขนั้นมีหากหลายรูปเเบบ เช่น การเล่นเกมเเบบตาเดียว , การเล่นซ้ำๆ จนกว่าผู้เล่นจะออกจากเกม ในส่วนของการรับค่านั้น หากผู้ใช้กรอกอักษรพิมพ์เล็กหรือพิมพ์ใหญ่ จะเเก้ปัญหาโดยการรับค่าเเบบ  enum.IntEnum โดยการเปลี่ยนจากการรับค่าเป็นสตริงเป็นการเเทนตัวเเปรโดยใช้ตัวเลขเเทน
  
Reference : (https://realpython.com/python-rock-paper-scissors/