บทความนี้แนะนำการเขียนเกมเตตริส (Tetris) แบบง่าย โดยแสดงผลในตารางขนาด กว้าง 10 ช่อง และสูง 16 ช่อง ตามภาพที่ 1 โดยใช้บอร์ดไมโครคอนโทรลเลอร์ esp32 ที่ต่อกับจอแสดงผลแบบ ST7735 และสวิตช์สำหรับควบคุมอีก 8 ตัว ที่สำคัญคือ เขียนด้วยภาษาไพธอนผ่าน MicroPython ที่คอมไพล์ให้ใช้ชุดไลบรารี st7735_mpy ซึ่งในบทความนี้กล่าวถึงการจัดเก็บวัตถุทั้ง 7 แบบที่เป็นสิ่งของหรือวัตถุที่ตกลงมาให้รองรับการแสดงผลและการหมุนวัตถุ กับการเลื่อนวัตถุไปทางซ้ายและขวา ส่วนการควบคุมและตรรกะของเกม Tetris จะกล่าวในบทความถัดไป

ภาพที่ 1 ตัวอย่างเกมของบทความนี้

โครงสร้างข้อมูล

เกม Tetris มีข้อมูลหลักที่ต้องจัดเก็บคือ วัตถุที่ตกลงมาทั้ง 7 แบบ ดังภาพที่ 1 ถึง 8 ซึ่งจะพบว่า ถูกเก็บในลักษะของตาางข้อมูลขนาด 4×4 โดยถ้าตำแหน่งใดเป็นส่วนของวัตถุจะเก็บค่า 1 แต่ถ้าตำแหน่งใดไม่ใช่ตัววัตถุจะเก็บเป็นค่า 0 โดยโครงสร้างข้อมูลของวัตถุแต่ละแบบเป็นดังนี้

วัตถุแบบที่ 1

วัตถุแบบแรกเป็นแท่งยาว 4 ช่อง สูง 1 ช่องดังภาพที่ 2 และเมื่อกดหมุนจะกลายเป็นแท่งสูง 4 ช่อง และกว้าง 1 ช่อง

ภาพที่ 2 วัตถุแบบที่ 1

การจัดเก็บข้อมูลของวัตถุที่ 1 เพื่อให้รองรับการหมุนได้ 2 แบบ เป็นดังนี้

    [ # แบบ1
     [[1,1,1,1], # rotate=0
      [0,0,0,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[1,0,0,0], # rotate=1
      [1,0,0,0],
      [1,0,0,0],
      [1,0,0,0]]
    ]

วัตถุแบบที่ 2

วัตถุแบบที่ 2 เป็นตามภาพที่ 3 สามารถหมุนได้ 4 แบบ และให้ผลในเรื่องของขนาดของวัตถุเปลี่ยนแปลงเป็น คือ เป็น 2×3, 3×2, 2×3 และ 3×2 ดังภาพ

ภาพที่ 3 วัตถุแบบที่ 2

การจัดเก็บข้อมูลของวัตถุที่ 2 เพื่อให้รองรับการหมุนได้ 4 แบบ เป็นดังนี้

    [ # แบบ 2
     [[1,1,1,0], # rotate=0
      [1,0,0,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[1,1,0,0], # rotate=1
      [0,1,0,0],
      [0,1,0,0],
      [0,0,0,0]],
     [[0,0,1,0], # rotate=2
      [1,1,1,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[1,1,0,0], # rotate=3
      [1,0,0,0],
      [1,0,0,0],
      [0,0,0,0]]
    ]

วัตถุแบบที่ 3

วัตถุประเภทนี้คล้ายกับแบบที่ 2 แต่การวางตำแหน่งตรงกันข้ามกันดังภาพที่ 4

ภาพที่ 4 วัตถุแบบที่ 3

การจัดเก็บข้อมูลของวัตถุที่ 3 เพื่อให้รองรับการหมุนได้ 4 แบบ เป็นดังนี้

    [ # แบบ 3
     [[1,0,0,0], # rotate=0
      [1,1,1,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[1,1,0,0], # rotate=1
      [1,0,0,0],
      [1,0,0,0],
      [0,0,0,0]],
     [[1,1,1,0], # rotate=2
      [0,0,1,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[0,1,0,0], # rotate=3
      [0,1,0,0],
      [1,1,0,0],
      [0,0,0,0]]
    ]

วัตถุแบบที่ 4

วัตถุแบบที่ 4 เป็นแท่งยาว 3 ช่องที่มีส่วนที่ยื่นออกมาตรงกลางดังภาพที่ 5 ทำให้ขนาดของวัตถุเมื่อมีการหมุนในมุมต่าง ๆ เป็น 2×3, 3×2, 2×3 และ 3×2

ภาพที่ 5 วัตถุแบบที่ 4

การจัดเก็บข้อมูลของวัตถุที่ 4 เพื่อให้รองรับการหมุนได้ 4 แบบ เป็นดังนี้

    [ # แบบ 4
     [[0,1,0,0], # rotate=0
      [1,1,1,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[1,0,0,0], # rotate=1
      [1,1,0,0],
      [1,0,0,0],
      [0,0,0,0]],
     [[1,1,1,0], # rotate=2
      [0,1,0,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[0,1,0,0], # rotate=3
      [1,1,0,0],
      [0,1,0,0],
      [0,0,0,0]],
    ]

วัตถุแบบที่ 5

วัตถุแบบที่ 5 มีขนาด 2×3 และเมื่อหมุนจะกลายเป็น 3×2 ดังภาพที่ 6

ภาพที่ 6 วัตถุแบบที่ 5

การจัดเก็บข้อมูลของวัตถุที่ 5 เพื่อให้รองรับการหมุนได้ 2 แบบ เป็นดังนี้

    [ # แบบ 5
     [[0,1,1,0], # rotate=0
      [1,1,0,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[1,0,0,0], # rotate=1
      [1,1,0,0],
      [0,1,0,0],
      [0,0,0,0]]
    ]

วัตถุแบบที่ 6

วัตถุแบบที่ 6 เป็นวัตถุที่อยู่ตรงกันข้ามกับวัตถุแบบที่ 5 ดังภาพที่ 7

ภาพที่ 7 วัตถุแบบที่ 6

การจัดเก็บข้อมูลของวัตถุที่ 6 เพื่อให้รองรับการหมุนได้ 2 แบบ เป็นดังนี้

    [ # แบบ 6
     [[1,1,0,0], # rotate=0
      [0,1,1,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[0,1,0,0], # rotate=1
      [1,1,0,0],
      [1,0,0,0],
      [0,0,0,0]]
    ]

วัตถุแบบที่ 7

วัตถุประเภทนี้มีขนาด 2×2 เหมือนกล่องสี่เหลี่ยมจึงทำให้หมุนอย่างไรก็ได้รูปทรงเดิม จึงไม่จำเป็นต้องหมุนวัตถุดังภาพที่ 8

ภาพที่ 8 วัตถุแบบที่ 7

การจัดเก็บข้อมูลของวัตถุที่ 7 เป็นดังนี้

    [ # แบบ 7
     [[1,1,0,0], # rotate=0
      [1,1,0,0],
      [0,0,0,0],
      [0,0,0,0]]
    ]

การเคลื่อนที่

อุปกรณ์นำเข้าประกอบด้วยสวิตช์ 8 ตัวที่ต่อแบบ Pull-up โดยกำหนดชื่อเรียก ดังภาพที่ 9 และ 10

ภาพที่ 9 สวิตช์ควบคุมทิศทางและ A,B
ภาพที่ 10 สวิตช์ M1 และ M2

การกำหนดค่าสำหรับสวิตช์ทั้ง 8 เป็นดังนี้

keL = Pin(39, Pin.IN, Pin.PULL_UP)
keU = Pin(34, Pin.IN, Pin.PULL_UP)
keD = Pin(35, Pin.IN, Pin.PULL_UP)
keR = Pin(32, Pin.IN, Pin.PULL_UP)

swM1 = Pin(33, Pin.IN, Pin.PULL_UP)
swM2 = Pin(25, Pin.IN, Pin.PULL_UP)
swA = Pin(26, Pin.IN, Pin.PULL_UP)
swB = Pin(27, Pin.IN, Pin.PULL_UP)

การสุ่มวัตถุ

การสุ่มวัตถุใช้การสุ่มจากคลาส random โดยกำหนดช่วงค่าเป็น 0 ถึง 6 แทนวัตถุประเภทที่ 1 ถึง 7 ซึ่งการทำงานของการสุ่มเมื่อกด swM2 เป็นดังนี้

        actorNo = random.randint(0,len(actors)-1)

การเลื่อนไปทางซ้าย

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

        if posX > 0:
            table()
            posX -= 1
            draw()

การเลื่อนไปทางขวาของวัตถุประเภท 1

หลักการทำงานของการเลื่อนวัตถุไปทางขวาจะอาศัยค่า posX และ actorRotate เป็นตัวแปรสำหรับพิจารณาการเลื่อน ดังนี้

  • ถ้า actorRotate เป็น 0 หมายถึงวัตถุมีความยาว 4 ช่อง ดังนั้น ถ้าพื้นที่ทางขวาไม่มากพอจะไม่ขยับ แต่ถ้าพอจะทำการขยับไปทางขวาด้วยการเพิ่มค่า posX
  • ถ้า actorRotate เป็น 1 ทำให้วัตถุมีความกว้างเป็น 1 ดังนั้น ถ้าวัตถุยังไม่ชิดช่องด้านขวาจะเพิ่มค่า posX

โค้ดของการทำงานเป็นดังนี้

            if (actorRotate == 0):
                maxX = maxCol-4
            else:
                maxX = maxCol-1
            if posX < maxX:
                table()
                posX += 1
                draw()

การเลื่อนไปทางขวาของวัตถุประเภท 2

หลักการทำงานของการเลื่อนวัตถุไปทางขวาของวัตถุประเภทที่ 2 นั้นอาศัยค่า posX และ actorRotate เป็นตัวแปรสำหรับพิจารณาการเลื่อน ดังนี้

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

โค้ดของการทำงานเป็นดังนี้

            if (actorRotate == 0):
                maxX = maxCol-3
            elif (actorRotate == 1):
                maxX = maxCol-2
            elif (actorRotate == 2):
                maxX = maxCol-3
            else:
                maxX = maxCol-2
            if posX < maxX:
                table()
                posX += 1
                draw()

การเลื่อนไปทางขวาของวัตถุประเภท 3

หลักการทำงานของการเลื่อนวัตถุไปทางขวาของวัตถุประเภทที่ 3 จะเหมือนกับแบบที่ 2 โดยโค้ดของการทำงานเป็นดังนี้

            if (actorRotate == 0):
                maxX = maxCol-3
            elif (actorRotate == 1):
                maxX = maxCol-2
            elif (actorRotate == 2):
                maxX = maxCol-3
            else:
                maxX = maxCol-2
            if posX < maxX:
                table()
                posX += 1
                draw()

การเลื่อนไปทางขวาของวัตถุประเภท 4

การขยับวัตถุประเภทที่ 4 ไปทางขวาจะมีหลักการเช่นเดียวกับวัตถุประเภทที่ 2 และ 3 ดังนี้

            if (actorRotate == 0):
                maxX = maxCol-3
            elif (actorRotate == 1):
                maxX = maxCol-2
            elif (actorRotate == 2):
                maxX = maxCol-3
            else:
                maxX = maxCol-2
            if posX < maxX:
                table()
                posX += 1
                draw()

การเลื่อนไปทางขวาของวัตถุประเภท 5

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

            if (actorRotate == 0):
                maxX = maxCol-3
            else:
                maxX = maxCol-2
            if posX < maxX:
                table()
                posX += 1
                draw()

การเลื่อนไปทางขวาของวัตถุประเภท 6

การเคลื่อนที่วัตถุประเภทที่ 6 มีหลัการเหมือนกับวัตถุประเภทที่ 6 ดังโค้ดต่อไปนี้

            if (actorRotate == 0):
                maxX = maxCol-3
            else:
                maxX = maxCol-2
            if posX < maxX:
                table()
                posX += 1
                draw()

การเลื่อนไปทางขวาของวัตถุประเภท 7

การย้ายวัตถุประเภทที่ 7 ไปทางขวาจะต้องมีพื้นที่ทางขวาอย่างน้อย 2 ช่อง ดังโค้ดต่อไปนี้

            if posX < (maxCol-2):
                table()
                posX += 1
                draw()

การหมุนวัตถุประเภทที่ 1

การหมุนวัตถุประเภทที่ 1 จะหมุนจาก 1 เป็น 0 ได้จะต้องมีพื้นที่ทางขวาเหลืออย่างน้อย 4 ช่อง ดังการตรวจสอบด้วยโค้ดต่อไปนี้

            actorRotate += 1
            if actorRotate > 1:
                if (posX > (maxCol-4)): # ถ้าตำแหน่งเกินขอบให้หมุนกลับไปตำแหน่งที่เหมาะสม
                    actorRotate = 1
                else:
                    actorRotate = 0

การหมุนวัตถุประเภทที่ 2

สำหรับวัตถุประเภทที่ 2 มีหลักการตรวจสอบการหมุนดังนี้

  • เปลี่ยนจากการหมุนแบบ 0 ไป 1 จะต้องมีพื้นที่ทางขวาเหลือ 2 หน่วย
  • เปลี่ยนจากการหมุนแบบ 1 ไป 2 จะต้องมีพื้นที่ทางขวเหลือ 3 หน่วย
  • เปลี่ยนจากการหมุนแบบ 2 ไป 3 จะต้องมีพื้นที่ทางขวาเหลืออยู่อย่างน้อย 2 หน่วย
  • เปลี่ยนจากการหมุนแบบ 3 ไป 0 จะต้องมีพื้นที่ทางขวาเหลืออยู่อย่างน้อย 3 หน่วย
  • กรณีอื่น ๆ จะไม่สามารถหมุนได้

โค้ดของการตรวจสอบการหมุนเป็นดังนี้

            if actorRotate == 0:
                actorRotate = 1
            elif actorRotate == 1:
                if (posX < (maxCol - 2)):
                    actorRotate = 2
                else:
                    actorRotate = 1
            elif actorRotate == 2:
                actorRotate = 3
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                else:
                    actorRotate = 3

การหมุนวัตถุประเภทที่ 3

มีหลักการเช่นเดียวกับวัตถุประเภทที่ 2 ดังโค้ดต่อไปนี้

            if actorRotate == 0:
                actorRotate = 1
            elif actorRotate == 1:
                if (posX < (maxCol - 2)):
                    actorRotate = 2
                else:
                    actorRotate = 1
            elif actorRotate == 2:
                actorRotate = 3
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                else:
                    actorRotate = 3

การหมุนวัตถุประเภทที่ 4

สำหรับการหมุนของวัตถุประเภทที่ 4 นั้นมีหลักการเหมือนกับการหมุนวัตถุประเภทที่ 2 และ 3 ดังโค้ดของการตรวจสอบการหมุนต่อไปนี้

            if actorRotate == 0:
                actorRotate = 1
            elif actorRotate == 1:
                if (posX < (maxCol - 2)):
                    actorRotate = 2
                else:
                    actorRotate = 1
            elif actorRotate == 2:
                actorRotate = 3
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                else:
                    actorRotate = 3

การหมุนวัตถุประเภทที่ 5

การหมุนวัตถุประเภทที่ 5 มีเงื่อนไขการทำงานดังนี้

  • หมุนจากสถานะ 0 เป็น 1 จะต้องมีพื้นที่ทางขวาเหลือ 2 ช่อง
  • เปลี่ยนจากสถานะ 1 เป็น 0 จะต้องมีพื้นที่ทางขวาเหลืออยู่อย่างน้อย 3 ช่อง
  • กรณีอื่น ๆ จะไม่สามารถหมุนได้

สำหรับโค้ดตรวจสอบการหมุนวัตถุประเภทที่ 5 คือ

            if actorRotate == 0:
                actorRotate = 1
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                else:
                    actorRotate = 1

การหมุนวัตถุประเภทที่ 6

การหมุนวัตถุประเภทที่ 6 มีหลักการเหมือนกับการหมุนวัตถุประเภทที่ 5 ดังโค้ดต่อไปนี้

            if actorRotate == 0:
                actorRotate = 1
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                else:
                    actorRotate = 1

การหมุนวัตถุประเภทที่ 7

เนื่องจากวัตถุประเภทที่ 7 เป็นสี่เหลี่ยมจัตุรัสขนาด 2×2 ทำให้การหมุนมีค่าเท่าเดิมจึงข้ามการตรวจสอบการหมุนของวัตถุประเภทนี้

ตัวอย่างโปรแกรม

จากโครงสร้างข้อมูลและการกำหนดเกี่ยวกับการรับค่าพร้อมทั้งเงื่อนไขของการรับค่า/แสดงผล เขียนโค้ดได้ดังนี้ โดยกำหนดให้

  • swM1 สำหรับออกจากโปรแกรม
  • swM2 สำหรับสุ่มวัตถุใหม่
  • swA สำหรับหมุนวัตถุ
  • keL สำหรับเลื่อนตำแหน่งวัตถุไปทางซ้าย
  • keR สำหรับเลื่อนตำแหน่งวัตถุไปทางขวา
#################################################################
# tetris
# JarutEx 2021-11-06
#################################################################
import gc
import os
import sys
import time
import machine as mc
from machine import Pin,SPI
import math
import st7735 as tft
import vga1_16x16 as font
import random
#################################################################
###### setting ##################################################
#################################################################
gc.enable()
gc.collect()

mc.freq(240000000)

spi = SPI(2, baudrate=26000000,
          sck=Pin(14), mosi=Pin(12),
          polarity=0, phase=0)
#### Key
keL = Pin(39, Pin.IN, Pin.PULL_UP)
keU = Pin(34, Pin.IN, Pin.PULL_UP)
keD = Pin(35, Pin.IN, Pin.PULL_UP)
keR = Pin(32, Pin.IN, Pin.PULL_UP)

swM1 = Pin(33, Pin.IN, Pin.PULL_UP)
swM2 = Pin(25, Pin.IN, Pin.PULL_UP)
swA = Pin(26, Pin.IN, Pin.PULL_UP)
swB = Pin(27, Pin.IN, Pin.PULL_UP)

spk = Pin(19,Pin.OUT)
spk.on()
time.sleep(0.1)
spk.off()

# dc, rst, cs
scr = tft.ST7735(spi, 128, 160, dc=Pin(15, Pin.OUT), reset=Pin(13,Pin.OUT), cs=Pin(2, Pin.OUT), rotation=3)
scr.initr()

actors = [
    [ # แบบ1
     [[1,1,1,1], # rotate=0
      [0,0,0,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[1,0,0,0], # rotate=1
      [1,0,0,0],
      [1,0,0,0],
      [1,0,0,0]]
    ],
    [ # แบบ 2
     [[1,1,1,0], # rotate=0
      [1,0,0,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[1,1,0,0], # rotate=1
      [0,1,0,0],
      [0,1,0,0],
      [0,0,0,0]],
     [[0,0,1,0], # rotate=2
      [1,1,1,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[1,1,0,0], # rotate=3
      [1,0,0,0],
      [1,0,0,0],
      [0,0,0,0]]
    ],
    [ # แบบ 3
     [[1,0,0,0], # rotate=0
      [1,1,1,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[1,1,0,0], # rotate=1
      [1,0,0,0],
      [1,0,0,0],
      [0,0,0,0]],
     [[1,1,1,0], # rotate=2
      [0,0,1,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[0,1,0,0], # rotate=3
      [0,1,0,0],
      [1,1,0,0],
      [0,0,0,0]]
    ],
    [ # แบบ 4
     [[0,1,0,0], # rotate=0
      [1,1,1,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[1,0,0,0], # rotate=1
      [1,1,0,0],
      [1,0,0,0],
      [0,0,0,0]],
     [[1,1,1,0], # rotate=2
      [0,1,0,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[0,1,0,0], # rotate=3
      [1,1,0,0],
      [0,1,0,0],
      [0,0,0,0]],
    ],
    [ # แบบ 5
     [[0,1,1,0], # rotate=0
      [1,1,0,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[1,0,0,0], # rotate=1
      [1,1,0,0],
      [0,1,0,0],
      [0,0,0,0]]
    ],
    [ # แบบ 6
     [[1,1,0,0], # rotate=0
      [0,1,1,0],
      [0,0,0,0],
      [0,0,0,0]],
     [[0,1,0,0], # rotate=1
      [1,1,0,0],
      [1,0,0,0],
      [0,0,0,0]]
    ],
    [ # แบบ 7
     [[1,1,0,0], # rotate=0
      [1,1,0,0],
      [0,0,0,0],
      [0,0,0,0]]
    ]
]
actorColors = [
    tft.color565(232,232,64),
    tft.color565(232,64,64),
    tft.color565(232,64,232),
    tft.color565(64,64,232),
    tft.color565(64,232,64),
    tft.color565(64,232,232),
    tft.color565(232,232,232)
]
actorRotate = 0
actorNo = random.randint(0,len(actors)-1)
maxCol = 10
maxRow = 16
posX = 0
posY = 0

#################################################################
###### sub modules ##############################################
#################################################################
def splash():
    scr.fill(tft.color565(0x00,0x00,0x00))
    scr.text(font,"JarutEx", 20, 20, tft.YELLOW, tft.BLACK)
    scr.text(font,"JarutEx", 21, 20, tft.YELLOW, tft.BLACK)
    scr.text(font,"(C)2021", 40,48, tft.CYAN, tft.BLACK)
    time.sleep_ms(2000)
    scr.fill(tft.BLACK)

def table():
    blankColor = tft.color565(48, 48, 48)
    for i in range(maxRow):
        for j in range(maxCol):
            x = j * 8 + 1
            y = i * 8 + 1
            w = 6
            h = 6
            scr.fill_rect(x,y,w,h, blankColor)

def draw():
    actor = actors[actorNo][actorRotate]
    for i in range(4):
        for j in range(4):
            if actor[i][j]:
                x = (posX+j) * 8 + 1
                y = (posY+i) * 8 + 1
                w = 6
                h = 6
                scr.fill_rect(x,y,w,h,actorColors[actorNo])


#################################################################
###### main program #############################################
#################################################################
splash()
table()
draw()
while swM1.value():
    if swM2.value() == 0:
        table()
        actorNo = random.randint(0,len(actors)-1)
        posX = 0
        posY = 0
        actorRotate = 0
        draw()
    if swA.value() == 0: # rotate
        table()
        if (actorNo == 0):
            actorRotate += 1
            if actorRotate > 1:
                if (posX > (maxCol-4)): # ถ้้าตำแหน่งเกินขอบให้หมุนกลับไปตำแหน่งที่เหมาะสม
                    actorRotate = 1
                else:
                    actorRotate = 0
        elif (actorNo == 1):
            if actorRotate == 0:
                actorRotate = 1
            elif actorRotate == 1:
                if (posX < (maxCol - 2)):
                    actorRotate = 2
                else:
                    actorRotate = 1
            elif actorRotate == 2:
                actorRotate = 3
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                else:
                    actorRotate = 3
        elif (actorNo == 2):
            if actorRotate == 0:
                actorRotate = 1
            elif actorRotate == 1:
                if (posX < (maxCol - 2)):
                    actorRotate = 2
                else:
                    actorRotate = 1
            elif actorRotate == 2:
                actorRotate = 3
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                else:
                    actorRotate = 3
        elif (actorNo == 3):
            if actorRotate == 0:
                actorRotate = 1
            elif actorRotate == 1:
                if (posX < (maxCol - 2)):
                    actorRotate = 2
                else:
                    actorRotate = 1
            elif actorRotate == 2:
                actorRotate = 3
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                else:
                    actorRotate = 3
        elif (actorNo == 4):
            if actorRotate == 0:
                actorRotate = 1
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                else:
                    actorRotate = 1
        elif (actorNo == 5):
            if actorRotate == 0:
                actorRotate = 1
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                else:
                    actorRotate = 1
        draw()
    if keL.value() == 0:
        if posX > 0:
            table()
            posX -= 1
            draw()
    if keR.value() == 0:
        if (actorNo == 0):
            if (actorRotate == 0):
                maxX = maxCol-4
            else:
                maxX = maxCol-1
            if posX < maxX:
                table()
                posX += 1
                draw()
        elif (actorNo == 1):
            if (actorRotate == 0):
                maxX = maxCol-3
            elif (actorRotate == 1):
                maxX = maxCol-2
            elif (actorRotate == 2):
                maxX = maxCol-3
            else:
                maxX = maxCol-2
            if posX < maxX:
                table()
                posX += 1
                draw()
        elif (actorNo == 2):
            if (actorRotate == 0):
                maxX = maxCol-3
            elif (actorRotate == 1):
                maxX = maxCol-2
            elif (actorRotate == 2):
                maxX = maxCol-3
            else:
                maxX = maxCol-2
            if posX < maxX:
                table()
                posX += 1
                draw()
        elif (actorNo == 3):
            if (actorRotate == 0):
                maxX = maxCol-3
            elif (actorRotate == 1):
                maxX = maxCol-2
            elif (actorRotate == 2):
                maxX = maxCol-3
            else:
                maxX = maxCol-2
            if posX < maxX:
                table()
                posX += 1
                draw()
        elif (actorNo == 4):
            if (actorRotate == 0):
                maxX = maxCol-3
            else:
                maxX = maxCol-2
            if posX < maxX:
                table()
                posX += 1
                draw()
        elif (actorNo == 5):
            if (actorRotate == 0):
                maxX = maxCol-3
            else:
                maxX = maxCol-2
            if posX < maxX:
                table()
                posX += 1
                draw()
        elif (actorNo == 6):
            if posX < (maxCol-2):
                table()
                posX += 1
                draw()
    time.sleep_ms(100)
scr.fill(0)
spi.deinit()

สรุป

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

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

(C) 2020-2022, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-11-07, 2022-01-10