ทบทวน Python กับไลฟ์การทำ Wordle ของน้อง Mari Taiga
พอดีมีคนแชร์ event น้องเขา แล้วน่าสนใจดี เลยมาฟังตอนสองทุ่มของวันอาทิตย์ที่ 23 มกราคม 2564 จ้า ไลฟ์นี้ 45 นาที สั้นกระชับ เข้าใจง่ายจ้า
ไลฟ์ย้อนหลังอยู่นี่น้าาา
.
ก่อนอื่นแนะนำน้องกันก่อนเลย น้องเขาชื่อว่ามะลิ ไทกะ เป็น Vtuber สายโปรแกรมมิ่ง ซึ่งก็ทำงานสายโปรแกรมเมอร์จริงๆ ในงานน้องเขียนภาษา Go นะ
ทำความเข้าใจวิธีการเล่น 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 แบบนี้
เราจะทำการ 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 นี้ได้ที่นี่
จากนั้นลองรันโค้ดของเราดูด้วยคำสั่งนี้
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 ได้ที่นี่เลย
จากนั้นสร้าง 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 น้องเขาก็มีอัพเดตตรงนี้อยู่
จริงๆเนี่ยการที่ไม่ 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 เร็วๆในนี้จ้า
ติดตามข่าวสารและบทความใหม่ๆได้ที่
Subscribe ช่อง YouTube ของเราได้ที่
download แอพอ่านบล็อกใหม่ของเราได้ที่นี่