ทบทวน Python กับไลฟ์การทำ Wordle ของน้อง Mari Taiga

Programming Jan 25, 2022

พอดีมีคนแชร์ event น้องเขา แล้วน่าสนใจดี เลยมาฟังตอนสองทุ่มของวันอาทิตย์ที่ 23 มกราคม 2564 จ้า ไลฟ์นี้ 45 นาที สั้นกระชับ เข้าใจง่ายจ้า

ไลฟ์ย้อนหลังอยู่นี่น้าาา

.


ก่อนอื่นแนะนำน้องกันก่อนเลย น้องเขาชื่อว่ามะลิ ไทกะ เป็น Vtuber สายโปรแกรมมิ่ง ซึ่งก็ทำงานสายโปรแกรมเมอร์จริงๆ ในงานน้องเขียนภาษา Go นะ

Mari Taiga
Mari Taiga. 1,153 likes · 48 talking about this. ชื้อ มะลิ ไทกะ เป็น Vtuber ค่ะ Vtuber https://www.youtube.com/channel/UCEX5ij7mqZiKdFZjyokn2vA Tiktok: https://tiktok.com/@maritaigaTwitter:...

ทำความเข้าใจวิธีการเล่น Wordle กันก่อน

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

เงื่อนไขในเกมส์หลังจากที่เราพิมพ์คำลงไป

  • สีเขียว : ตัวอักษรนี้อยู่ในตำแหน่งที่ถูกต้องแล้ว
  • สีเหลือง : มีอักษรนี้อยู่ในคำ แต่ยังไม่ถูกตำแหน่งนะ
  • สีเทา : ไม่มีตัวอักษรนี้

ตัวเกมส์จะให้เราเดาคำที่มีตัวอักษร 5 ตัว และสามารถเดาคำที่ถูกต้องได้เพียง 6 ครั้งเท่านั้น

ดังนั้นสิ่งที่น้องมะลิ live code ให้ดูจะมี flow ดังนี้

  • random คำจากใน list ออกมา
  • เริ่มเกมส์
  • ให้คนเล่นเดาคำ
  • ตรวจสอบว่าคำนี้มัน valid ไหม โดยมี 2 เงื่อนไขที่ต้องผ่าน คือ ความยาวตรงกัน (เนื่องจากโปรแกรมนี้จะเล่นผ่าน command line) และเป็นคำที่มีความหมายจริงๆ ไม่ได้พิมพ์มั่วอะไรงี้
  • แสดง character
  • ตรวจสอบว่าคนเล่นเล่นชนะหรือไม่

เริ่มทำการ coding กันเลย

เนื่องจากน้องมะลิทำการ coding บน Windows แต่เรา coding บน MacOS ดังนั้นจะมีบางส่วนที่แตกต่างกันไปตาม OS นะ

ดึง list ออกมา และเอา response ที่ได้มาใช้

โดยน้องมะลิเลือก list คำจากเว็บของ MIT หน้าตาจะเป็น plain text แบบนี้

https://www.mit.edu/~ecprice/wordlist.10000

เราจะทำการ download library ผ่าน pip กัน สำหรับ macos สามารถใช้ command นี้ทำการ install เจ้า pip กันก่อน

python3 -m pip

โค้ดในส่วนนี้ เราจะทำการดึงข้อมูลจากเว็บนี้ออกมา และนำส่วน response.content มาใช้ response ที่ได้จะเป็น byte นะ

import requests

if __name__ == "__main__":
    url = "https://www.mit.edu/~ecprice/wordlist.10000"
    response = requests.get(url)
    print(response.content)

เนื่องจากเราใช้ library ภายนอก คือ requests ดังนั้นเราจะต้องไป install ลงเครื่องก่อน

python3 -m pip install requests

อ่าน document ของ library นี้ได้ที่นี่

Requests: HTTP for Humans — Requests 2.2.1 documentation
https://docs.python-requests.org/en/v2.2.1/

จากนั้นลองรันโค้ดของเราดูด้วยคำสั่งนี้

python3 wordle.py

ผลที่ได้จะต้องได้แบบนี้นะ

ผลที่ได้คืออ่านยาก เพราะตัวอักษรติดกันไปหมด แต่เราจะเห็นว่ามี \n อยู่ในนั้นเป็นการคั่นว่ามันขึ้นบรรทัดใหม่เนอะ ดังนั้นเราจะทำการ split new line ออกไป

all_words = response.content.spltilines()

แล้วเราทำการ filter เฉพาะคำที่มีความยาว 5 ตัวอักษรเท่านั้น สามารถทำได้ 2 แบบ คือ

# แบบ for-loop แล้ว check ความยาว
words = [word for word in all_words if len(word) == 5]

# filter ว่าคำนั้นมีความยาวเท่ากับ 5, อันนี้แอบคล้าย kotlin อยู่หน่อยๆ มี lambda ด้วย
words = filter(lambda word: len(word) == 5, all_words)

ส่วนตัวเราชอบท่า filter มากกว่าเพราะคล้าย kotlin 555 ผลที่ได้จะเป็น object เนอะ แต่เดี๋ยวมาต่อให้ตอนจบแล้วกัน ตอนนี้ตามน้องเขาไปก่อน

ในไลฟ์น้องทะลิใช้ท่าแบบ for-loop แล้ว check ความยาว ผลที่ได้คือจะได้ตัว list ที่เป็น byte เนอะ

จากนั้นทำการ random คำใน list นี้ออกมาเป็นโจทย์ random.choice(words) ซึ่งผลออกมาเป็น byte อยู่ จึงทำการ decode ด้วย UTF-8

word = random.choice(words).decode("utf-8")

ผลที่ได้จะได้คำที่เป็น String ปกติมาหล่ะ

แสดงผลว่า game is come on!

จบในส่วนของคำโจทย์ไปแล้ว เราจะทำการเริ่มเกมส์กันเถอะ

ก่อนอื่นตั้งตัวแปรชื่อ num_guess โดยให้เท่ากับ 0 แล้วก็ตั้ง loop while true เพื่อให้มันลองให้เราเล่นไปเรื่อยๆก่อน เดี๋ยวค่อยมาเปลี่ยนกัน ข้างในใส่ตัว input เพื่อให้คนเล่นพิมพ์ผ่าน command line และรับสิ่งที่คนเล่นพิมพ์มาด้วยตัวแปร guess เพื่อเอาไปใช้ต่อ

num_guess = 0
while True:
    prompt = "Guess ({remaining} remaining) : ".format(remaining = 6 - num_guess)
    guess = input(prompt)

ตอนนี้เราก็ลองพิมพ์ดูการทำงานของโค้ดที่เราเขียนเพิ่มขึ้นมาแล้ว

จัดการ condition ต่างๆภายในเกมส์

ต่อมาสร้าง function เพื่อ check condition ว่าที่คนเล่นพิมพ์ไปนั้น มีตัวอักษร 5 ตัวอักษรหรือไม่ และคำนี้มีความหมายไหม

ก่อนอื่นมาลง library กันเพิ่มเติม ชื่อ pyenchant

python3 -m pip install pyenchant

สำหรับ macOS ไม่ได้อ่ะ ต้องลงผ่าน homebrew

brew install enchant

library pyenchant เป็น library ไว้ check เรื่อง spell นะ สามารถอ่าน document ได้ที่นี่เลย

pyenchant
Python bindings for the Enchant spellchecking system
Installation — PyEnchant 3.2.2 documentation

จากนั้นสร้าง function ที่มีชื่อว่า is_valid() ขึ้นมา เพื่อ check ความยาวของคำ และเรื่อง wording spell ตัว enchant จะใช้เป็นของภาษา en_US เนอะ

ถ้าคำที่คนเล่นพิมพ์มีความยาวเท่ากับ 5 และเป็นคำที่ wording spell ถูกต้อง ให้ return True ที่เหลือก็ return False ไป

import enchant

enchant_dict = enchant.Dict("en_US")

def is_valid(guess, word):
    if len(guess) != len(word):
        print("wrong length")
        return False
    if not enchat_dict.check(guess):
        print("not a valid guess")
        return False
    return True

แล้วก็เอาไปใช้ในโค้ดหลักแบบนี้

num_guess = 0
while True:
    prompt = "Guess ({remaining} remaining) : ".format(remaining = 6 - num_guess)
    guess = input(prompt)
    
    if not is_valid(guess, word):
        print ("invalid guess")
        continue

ลองรันจริงจะประมาณนี้เนอะ

จากนั้นมาทำการแสดงสีของตัวอักษร เป็น class ของสี โดยทางเราแอบตกใจกับการใส่ค่าสีของน้องมาก น้องใช้ ASCII color codes ปกติเราใช้แต่ hex color หน่ะสิ

สีหลักๆที่ใช้คือสีเขียว สีเหลือง และสีปกติ

จากนั้นสร้าง display_characters() ขึ้นมา เพื่อแสดงสีตาม condition ของเกมส์

def display_characters(guess, word):
    for i in range(len(guess)):
        if word[i] == guess[i]:
            print(colors.GREEN + guess[i], end='')
        elif guess[i] in word:
            print(colors.YELLOW + guess[i], end='')
        else:
            print(colors.NORMAL + guess[i], end='')
    print(colors.NORMAL)

การทำงานก็ประมาณนี้แหละ แต่หลายๆคนก็ไม่มีใน enchant อะเนอะ

แต่ยัง 6 remaining อยู่เลย ดังนั้นเราจะมาเพิ่มค่า num_guess ทีละ 1 กัน

num_guess = 0
while True:
    prompt = "Guess ({remaining} remaining) : ".format(remaining = 6 - num_guess)
    guess = input(prompt)

    if not is_valid(guess, word):
        print ("invalid guess")
        continue
        
    num_guess += 1

    display_characters(guess, word)

แต่เท่าที่เราลองเล่นไปเนี่ย ก็ไม่รู้ว่าคำที่เราใส่ถูกไหมหน่ะสิ แล้วถ้าครบ 6 แล้วจะยังไงต่อ เลยมาทำ check_win() กันต่อ ถ้าเราเดาคำถูก หรือจำนวนโควต้าในการเดาหมดแล้ว ให้ return true ถ้ายังเราไม่ถูกแล้วยังเหลือโควต้า ให้ return false ไป

def check_win(guess, word, num_guess):
    if guess == word:
        print("You win!! Congrats")
        return True
    elif num_guess == 6:
        print("Out of guesses :(")
        print("Answer was: ", word)
        return True
    else:
        print("Incorrect. Try again")
        return False

แต่ naming ชื่อแอบแปลกๆ เพราะตอนเราเรียกใช้ เราอยากรู้ว่าเกมส์จบยังอ่ะ อาจจะเปลี่ยนชื่อเป็นแบบนี้ดีกว่า

def check_end_game(guess, word, num_guess):
    if guess == word:
        print("You win!! Congrats")
        return True
    elif num_guess == 6:
        print("Out of guesses :(")
        print("Answer was: ", word)
        return True
    else:
        print("Incorrect. Try again")
        return False

กลับมาที่ function หลักของเรากัน เราจะเปลี่ยน condition การลูปจากเดิม while True เป็น while not is_end_game แทน แบบเกมส์ยังไม่จบก็เล่นต่อให้จบนะ

num_guess = 0
is_end_game = False

while not is_end_game:
    prompt = "Guess ({remaining} remaining) : ".format(remaining = 6 - num_guess)
    guess = input(prompt)

    if not is_valid(guess, word):
        print ("invalid guess")
        continue
        
    num_guess += 1

    display_characters(guess, word)

    is_end_game = check_end_game(guess, word, num_guesses)

มาลองเล่นกัน เดาถูกด้วยหล่ะ

ถ้าอยากเล่นต่อหล่ะ?

แน่นอนว่าเกมส์มันสนุก อยากเล่นต่อกันใช่ไหมหล่าาา~~~

แล้วเราเขียนโค้ดให้ replay ใหม่ได้ไหมนะ?

ได้สิ มาาาาาาาา!!!!!

ก่อนอื่นสร้าง  prompt มาเพิ่ม เพื่อรับ input จาก user ว่าอยากเล่นต่อไหม โดยถ้าสิ่งที่คนเล่นพิมพ์เป็น Y ก็คือค่า play_again เป็น True ถ้าไม่ใช่ก็เป็น False เราแอบปรับโค้ดเพิ่มจากของน้องเขานิดนึง เผื่อคนขี้เกียจพิมพ์เป็น y แทน

play_again_prompt = input("play again? Y (Yes)")
play_again = True if play_again_prompt.lower() == "y" else False

แล้วก็เพิ่ม for-loop ข้างนอกอีกชุดนึง ว่าเขาจะเล่นใหม่ไหม play_again ถ้าใช่ก็เล่นใหม่ ค่า default จะเป็น True เนอะ

num_guess = 0
play_again = True
is_end_game = False
    
while play_again:
    while not is_end_game:
        prompt = "Guess ({remaining} remaining) : ".format(remaining = 6 - num_guess)
        guess = input(prompt)

        if not is_valid(guess, word):
            print ("invalid guess")
            continue
            
        num_guess += 1

        display_characters(guess, word)

        is_end_game = check_end_game(guess, word, num_guess)
        
    play_again_prompt = input("play again? Y (Yes)")
    play_again = True if play_again_prompt.lower() == "y" else False

อ่ะลองเล่นดู รอบแรกตอบถูกเรียบร้อย เกมส์จบก็จะขึ้นมาว่า จะเล่นอีกม่ะ

อยากเล่นต่อแต่ไม่ยอมให้เล่นอ่ะดิ ก็เลยต้องออก

สาเหตุเพราะว่าบางค่าอย่าง num_guess กับ is_end_game ไม่ได้ reset ใหม่ แล้วก็คำในชุดใหม่ด้วย ดังนั้นเราจะต้องย้ายโค้ดพวกนี้มาก่อน for-loop is_end_game ก่อน

จากนั้นมาลองเล่นกันอีกที ตอนนี้เราเล่นใหม่ได้แล้วหล่ะ

โค้ดทั้งหมดจะเป็นแบบนี้จ้า

.


มีคนถามในไลฟ์ว่าเรื่อง response ไม่ใช่ 200 แล้วจะยังไงต่อ

เราเข้าใจน้องแหละว่าเราจะต้อง assume ก่อนว่าส่วนหน้าบ้านทำงานได้ แล้วค่อยๆทำไปทีละจุดจน logic หน้าบ้านครบ แล้วค่อยมาเก็บส่วนหลังบ้านทีหลัง ก่อนจบไลฟ์น้องก็ดูพวกนี้นะ

ใน github น้องเขาก็มีอัพเดตตรงนี้อยู่

GitHub - taiga-mari/Wordle
Contribute to taiga-mari/Wordle development by creating an account on GitHub.

จริงๆเนี่ยการที่ไม่ response 200 การจัดการการทำงานต่างๆ การวาง UI ในการทำงานจริง dev อาจจะไม่ได้เป็นคน design ทั้งหมดงี้ บางเคสก็ต้องสื่อสารให้ user เข้าใจด้วย ก็เป็นเรื่องที่แอบยุ่งยากนิดนึง

แต่ถ้าแบบไม่ response ไม่ 200 เราเองก็นึกไม่ออกว่าจะให้ทำอะไรนอกจากบอกว่าเกมส์เล่นไม่ได้หรอ อื้มมมมมม เรื่องนี้เราขอข้ามไปแล้วกันเนอะ แหะๆ


แล้วถ้าใช้อีกท่านึงหล่ะ

การที่เรา filter คำทั้งหมดจาก list ว่าเป็นคำที่มี 5 ตัวอักษรนั้น จะมี 2 ท่า คือ

# แบบ for-loop แล้ว check ความยาว
words = [word for word in all_words if len(word) == 5]

# filter ว่าคำนั้นมีความยาวเท่ากับ 5, อันนี้แอบคล้าย kotlin อยู่หน่อยๆ มี lambda ด้วย
words = filter(lambda word: len(word) == 5, all_words)

ถ้าเลือกท่า filter ตัว result ที่ได้เป็น object ดังนั้นเราจะต้องแปลง object เป็น list เพื่อให้เหมือนเพื่อนตอนท่าแรก โดยการ . . .

words = list(filter(lambda word: len(word) == 5, all_words))

ครอบ list() เพื่อแปลง type object เป็น list แบบนี้เลย

ที่เหลือก็ทำงานได้ตามปกติหล่ะ

จบบล็อกนี้แล้วจ้า สวัสดี


กด follow Twitter เพื่อได้รับข่าวสารก่อนใคร เช่น สปอย content ใหม่ หรือสรุป content เร็วๆในนี้จ้า

ติดตามข่าวสารและบทความใหม่ๆได้ที่

อย่าลืมกด like กด share บทความกันด้วยนะคะ :)

Posted by MikkiPastel on Sunday, 10 December 2017

Subscribe ช่อง YouTube ของเราได้ที่

mikkicoding
Android Developer & Content Creator
https://www.youtube.com/channel/UCtGbMSe4i7NJiKQ271Fezcg

download แอพอ่านบล็อกใหม่ของเราได้ที่นี่

MikkiPastel - Apps on Google Play
First application from “MikkiPastel” on play store beta feature- read blog from https://www.mikkipastel.com by this application- read blog content by chrome custom tab- update or refresh new content by pull to refresh- share content to social network
https://play.google.com/store/apps/details?id=com.mikkipastel.blog

Tags

Minseo Chayabanjonglerd

I am a full-time Android Developer and part-time contributor with developer community and web3 world, who believe people have hard skills and soft skills to up-skill to da moon.