[TH] Simple Tetris Ep.2

จากตอนที่แล้วเราได้วาดฉากหลัง การสุ่มวัตถุ การวาดวัตถุ การเลื่อนซ้ายขวา และการหมุนไปแล้ว ในบทความตอนที่ 2 ซึ่งเป็นตอนก่อนตอนสุดท้ายของชุดการทำเกม Tetris โดยเนื้อหาเป็นเรื่องของการสร้างฉากหลังเป็นโครงสร้างข้อมูลตาราง ถ้าวัตถุตกลงมาจนถึงล่างสุดจะแปลงวัตถุนั้นให้เป็นข้อมูลหนึ่งของตาราง ดังภาพที่ 1และปรับปรุงเรื่องวิธีการตกลงมาของวัตถุและการควบคุม/แสดงผลวัตถุใหม่ด้วยการใช้ตัวตั้งเวลา โดยยังไม่ตรวจสอบการชนจากการเลื่อนซ้าย/ขวา การตรวจสอบว่าวัตถุตกลงมาซ้อนกับวัตถุก่อนหน้านี้หรือไม่การหมุน และการตัดแถวซึ่งจะกล่าวถึงในบทความตอนสุดท้ายหรือ Simple Tetris Ep.3

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

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

ในบทความนี้มีการเพิ่มโครงสร้างข้อมูลสำหรับเก็บแผนที่ของฉากหลังซึ่งใช้สำหรับเก็บสถานะของเกมว่าตำแหน่งใดมีวัตถุค้างอยู่บ้าง โดยสร้างตัวแปรชื่อ field เป็นข้อมูลประเภทลิสต์ และมีการสร้างข้อมูลภายในเป็น 16 แถว แถวละ 10 ชุดข้อมูล พร้อมทั้งกำหนดให้มีค่าเริ่มต้นเป็น 0 ดังนี้

field = []
for i in range(maxRow):
    row = []
    for j in range(maxCol):
        row.append(0)
    field.append(row)

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

การให้วัตถุเลื่อนลงมา

การให้วัตถุที่สุ่มมานั้นตกลงมาจากด้านบนลงด้านล่างใช้วิธีการสร้างตัวตั้งเวลาให้ทำงานทุก 1 วินาที และปรับเปลี่ยนค่า posY ของตัวแปรระบบ โดยการใช้ตัวตั้งเวลามีโค้ดตัวอย่างดังนี้

from machine import Timer
Tmr = Timer(0)
Tmr.init( period=1000, mode=Timer.PERIODIC, callback=cbFalling)
...
Tmr.deinit()

จากโค้ดข้างต้นได้เรียกใช้ฟังก์เรียกกลับหรือ callback function ชื่อ cbFalling เพื่อให้ทำงานทุก 1000 มิลลิวินาที หรือ 1 วินาที โดยขั้นตอนการทำงานหลักของฟังก์ชันตัวนี้คือ ปรับค่า posY ให้มากขึ้น และวาดวัตถุนั้นในตำแหน่งแถว posY ในตาราง และถ้าเลื่อนลงมาจนถึงแถวสุดท้ายของตารางจะเปลี่ยนข้อมูลในตารางจากที่เป็น 0 ในตอนสร้างค่าให้กลายเป็น 1 หลังจากเปลี่ยนค่าในตารางจะเริ่มต้นการสุ่มวัตถุชิ้นใหม่ที่จะตกลงมา โดยหลักการทำงานของแต่ละส่วนเป็นดังนี้

การเพิ่มค่าและกำหนดค่าแถวสุดท้าย

การเพิ่มค่าแถวทำด้วยการกำหนดให้ตัวแปร posY มีค่าเพิ่มขึ้น 1 ด้วยการเขียนโค้ดดังนี้

posY += 1

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

การหาค่าแถวสุดท้ายที่วัตถุจะอยู่ได้ หรือตัวแปร lastRow ของแต่ละวัตถุมีหลักการดังนี้

  • วัตถุแบบที่ 1 พิจารณาจากค่าการหมุนดังนี้
    • หมุนแบบที่ 1 ให้ lastRow = 15 เนื่องจากสูง 1 หน่วย
    • หมุนแบบที่ 2 ให้ lastRow เป็น 12 เนื่องจากสูง 4 หน่วย
  • วัตถุแบบที่ 2 พิจารณาจากค่าการหมุนดังนี้
    • หมุนแบบที่ 1 ให้ lastRow เป็น 14 เนื่องจากสูง 2 หน่วย
    • หมุนแบบที่ 2 ให้ lastRow เป็น 13 เนื่องจากสูง 3 หน่วย
    • หมุนแบบที่ 3 ให้ lastRow เป็น 14 เนื่องจากสูง 2 หน่วย
    • หมุนแบบที่ 4 ให้ lastRow เป็น 13 เนื่องจากสูง 3 หน่วย
  • วัตถุแบบที่ 3 พิจารณาจากค่าการหมุนดังนี้
    • หมุนแบบที่ 1 ให้ lastRow เป็น 14 เนื่องจากสูง 2 หน่วย
    • หมุนแบบที่ 2 ให้ lastRow เป็น 13 เนื่องจากสูง 3 หน่วย
    • หมุนแบบที่ 3 ให้ lastRow เป็น 14 เนื่องจากสูง 2 หน่วย
    • หมุนแบบที่ 4 ให้ lastRow เป็น 13 เนื่องจากสูง 3 หน่วย
  • วัตถุแบบที่ 4 พิจารณาจากค่าการหมุนดังนี้
    • หมุนแบบที่ 1 ให้ lastRow เป็น 14 เนื่องจากสูง 2 หน่วย
    • หมุนแบบที่ 2 ให้ lastRow เป็น 13 เนื่องจากสูง 3 หน่วย
    • หมุนแบบที่ 3 ให้ lastRow เป็น 14 เนื่องจากสูง 2 หน่วย
    • หมุนแบบที่ 4 ให้ lastRow เป็น 13 เนื่องจากสูง 3 หน่วย
  • วัตถุแบบที่ 5 พิจารณาจากค่าการหมุนดังนี้
    • หมุนแบบที่ 1 ให้ lastRow เป็น 14 เนื่องจากสูง 2 หน่วย
    • หมุนแบบที่ 2 ให้ lastRow เป็น 13 เนื่องจากสูง 3 หน่วย
  • วัตถุแบบที่ 6 พิจารณาจากค่าการหมุนดังนี้
    • หมุนแบบที่ 1 ให้ lastRow เป็น 14 เนื่องจากสูง 2 หน่วย
    • หมุนแบบที่ 2 ให้ lastRow เป็น 13 เนื่องจากสูง 3 หน่วย
  • วัตถุแบบที่ 7 มีความสูง 2 หน่วยจึงได้ค่า lastRow เป็น 14

โค้ดโปรแกรมของการหาค่า lastRow เป็นดังนี้

    lastRow = 14
    if actorNo == 0:
        if actorRotate == 0:
            lastRow = 15
        else:
            lastRow = 12
    elif actorNo == 1:
        if actorRotate == 0:
            lastRow = 14
        elif actorRotate == 1:
            lastRow = 13
        elif actorRotate == 2:
            lastRow = 14
        else:
            lastRow = 13
    elif actorNo == 2:
        if actorRotate == 0:
            lastRow = 14
        elif actorRotate == 1:
            lastRow = 13
        elif actorRotate == 2:
            lastRow = 14
        else:
            lastRow = 13
    elif actorNo == 3:
        if actorRotate == 0:
            lastRow = 14
        elif actorRotate == 1:
            lastRow = 13
        elif actorRotate == 2:
            lastRow = 14
        else:
            lastRow = 13
    elif actorNo == 4:
        if actorRotate == 0:
            lastRow = 14
        else:
            lastRow = 13
    elif actorNo == 5:
        if actorRotate == 0:
            lastRow = 14
        else:
            lastRow = 13
    else:
        lastRow = 14

การเปลี่ยนให้ตารางมีค่าเป็น 1

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

        ### อัพเดต field
        for i in range(4):
            for j in range(4):
                if actors[actorNo][actorRotate][i][j] == 1:
                    field[posY+i-1][posX+j] = 1
        table()

การควบคุมการแสดงผล

ในการทำงานเรื่องการควบคุมการแสดงผลนั้นใช้ตัวตั้งเวลาชื่อ Render โดยสั่งงานไว้ดังนี้

Render = Timer(1)
Render.init( period=100, mode=Timer.PERIODIC, callback=cbRender)
...
Render.deinit()

จากโค้ดด้านบนจะพบว่ามีการกำหนดให้ตัวตั้งเวลาเรียก cbRender ทุก 100 มิลลิวินาที หรือ 10 ครั้งต่อวินาที โดยฟังก์ชันเรียกกลับมีส่วนการทำงาน 2 ส่วนหลัก คือ การนำเข้าข้อมูล และการแสดงผล

การรับข้อมูล

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

table() # ล้างหน้าจอแสดวาดตารางใหม่
...ประมวลผลต่าง ๆ
draw() # วาดวัตถุ

มาเป็นการใช้การตั้งการแสดงผลผ่านตัวแปร updated โดยกำหนดให้

  • เป็น False จะไม่มีการวาด
  • เป็น True จะมีการวาด

การวาด

เมื่อตัวแปร updated เป็นจริง จะดำเนินการวาดโดยมีหลักการทำงาน 2 ส่วน คือ

การหาแถวที่ต้องวาด

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

    if (updated):
        startY = 0
        endY = posY
        if posY > 0:
            startY = posY-1
        if endY >= maxRow:
            endY = maxRow
        if actorNo == 0:
            endY += 4
        elif actorNo == 1:
            endY += 3
        elif actorNo == 2:
            endY += 3
        elif actorNo == 3:
            endY += 3
        elif actorNo == 4:
            endY += 3
        elif actorNo == 5:
            endY += 3
        else:
            endY += 2
        if endY >= maxRow:
            endY = maxRow
        table(startY,endY)
        draw()
        updated = False

คำสั่งวาด

คำสั่งวาดนั้นมี 2 ส่วน คือ table() และ draw() โดยคำสั่ง draw() ยังคงทำงานเหมือนกับบทความก่อนหน้านี้ แต่คำสั่ง table() มีการเปลี่ยนแปลง โดยให้ผู้เขียนโปรแกรมสามารถระบุแถวเริ่มต้นที่จะวาด และแถวสุดท้ายที่จะวาดเพื่อลดภาระของการวาดในส่วนที่ไม่จำเป็นออกไป ทำให้การทำงานในภาพรวมนั้นดีขึ้น และโค้ดของ table() เป็นดังนี้

def table(rowStart=0, rowEnd=maxRow):
    for i in range(rowStart, rowEnd,1):
        for j in range(maxCol):
            x = j * 8 + 1
            y = i * 8 + 1
            w = 6
            h = 6
            if (field[i][j] == 0):
                scr.fill_rect(x,y,w,h, blankColor)
            else:
                scr.fill_rect(x,y,w,h, filledColor)

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

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

  • swM1 สำหรับออกจากโปรแกรม
  • swA สำหรับหมุนวัตถุ
  • keL สำหรับเลื่อนตำแหน่งวัตถุไปทางซ้าย
  • keR สำหรับเลื่อนตำแหน่งวัตถุไปทางขวา
#################################################################
# tetris Ep2
# JarutEx 2021-11-06
#################################################################
import gc
import os
import sys
import time
import machine as mc
from machine import Pin,SPI, Timer
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()

Tmr = Timer(0)

field = []
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
blankColor = tft.color565(48, 48, 48)
filledColor = tft.color565(192,192,192)
updated = False

#################################################################
###### 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 genTable():
    global field
    for i in range(maxRow):
        row = []
        for j in range(maxCol):
            row.append(0)
        field.append(row)

def table(rowStart=0, rowEnd=maxRow):
    for i in range(rowStart, rowEnd,1):
        for j in range(maxCol):
            x = j * 8 + 1
            y = i * 8 + 1
            w = 6
            h = 6
            if (field[i][j] == 0):
                scr.fill_rect(x,y,w,h, blankColor)
            else:
                scr.fill_rect(x,y,w,h, filledColor)

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])
                
def cbFalling(x):
    global posY,posX,actorRotate, actorNo, updated
    posY += 1
    ##### กำหนดแถวสุดท้าย
    lastRow = 14
    if actorNo == 0:
        if actorRotate == 0:
            lastRow = 15
        else:
            lastRow = 12
    elif actorNo == 1:
        if actorRotate == 0:
            lastRow = 14
        elif actorRotate == 1:
            lastRow = 13
        elif actorRotate == 2:
            lastRow = 14
        else:
            lastRow = 13
    elif actorNo == 2:
        if actorRotate == 0:
            lastRow = 14
        elif actorRotate == 1:
            lastRow = 13
        elif actorRotate == 2:
            lastRow = 14
        else:
            lastRow = 13
    elif actorNo == 3:
        if actorRotate == 0:
            lastRow = 14
        elif actorRotate == 1:
            lastRow = 13
        elif actorRotate == 2:
            lastRow = 14
        else:
            lastRow = 13
    elif actorNo == 4:
        if actorRotate == 0:
            lastRow = 14
        else:
            lastRow = 13
    elif actorNo == 5:
        if actorRotate == 0:
            lastRow = 14
        else:
            lastRow = 13
    else:
        lastRow = 14
            
    #### หยุดเมื่อถึงแถวสุดท้าย
    if (posY > lastRow):
        ### อัพเดต field
        for i in range(4):
            for j in range(4):
                if actors[actorNo][actorRotate][i][j] == 1:
                    field[posY+i-1][posX+j] = 1
        table()
        ### สุ่มวัตถุใหม่
        actorNo = random.randint(0,len(actors)-1)
        posX = 0
        posY = 0
        actorRotate = 0
    updated = True

def cbRender(x):
    global posY,posX,actorRotate, actorNo, updated
    ####################################################### 
    ##### Input
    ####################################################### 
    if swA.value() == 0: # rotate
        if (actorNo == 0):
            actorRotate += 1
            if actorRotate > 1:
                if (posX > (maxCol-4)): # ถ้้าตำแหน่งเกินขอบให้หมุนกลับไปตำแหน่งที่เหมาะสม
                    actorRotate = 1
                else:
                    actorRotate = 0
                    updated = True
            else:
                updated = True
        elif (actorNo == 1):
            if actorRotate == 0:
                actorRotate = 1
                updated = True
            elif actorRotate == 1:
                if (posX < (maxCol - 2)):
                    actorRotate = 2
                    updated = True
                else:
                    actorRotate = 1
            elif actorRotate == 2:
                actorRotate = 3
                updated = True
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                    updated = True
                else:
                    actorRotate = 3
        elif (actorNo == 2):
            if actorRotate == 0:
                actorRotate = 1
                updated = True
            elif actorRotate == 1:
                if (posX < (maxCol - 2)):
                    actorRotate = 2
                    updated = True
                else:
                    actorRotate = 1
            elif actorRotate == 2:
                actorRotate = 3
                updated = True
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                    updated = True
                else:
                    actorRotate = 3
        elif (actorNo == 3):
            if actorRotate == 0:
                actorRotate = 1
                updated = True
            elif actorRotate == 1:
                if (posX < (maxCol - 2)):
                    actorRotate = 2
                    updated = True
                else:
                    actorRotate = 1
            elif actorRotate == 2:
                actorRotate = 3
                updated = True
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                    updated = True
                else:
                    actorRotate = 3
        elif (actorNo == 4):
            if actorRotate == 0:
                actorRotate = 1
                updated = True
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                    updated = True
                else:
                    actorRotate = 1
        elif (actorNo == 5):
            if actorRotate == 0:
                actorRotate = 1
                updated = True
            else:
                if (posX < (maxCol - 2)):
                    actorRotate = 0
                    updated = True
                else:
                    actorRotate = 1
    if keL.value() == 0:
        if posX > 0:
            posX -= 1
            updated = True
    if keR.value() == 0:
        if (actorNo == 0):
            if (actorRotate == 0):
                maxX = maxCol-4
            else:
                maxX = maxCol-1
            if posX < maxX:
                posX += 1
                updated = True
        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:
                posX += 1
                updated = True
        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:
                posX += 1
                updated = True
        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:
                posX += 1
                updated = True
        elif (actorNo == 4):
            if (actorRotate == 0):
                maxX = maxCol-3
            else:
                maxX = maxCol-2
            if posX < maxX:
                posX += 1
                updated = True
        elif (actorNo == 5):
            if (actorRotate == 0):
                maxX = maxCol-3
            else:
                maxX = maxCol-2
            if posX < maxX:
                posX += 1
                updated = True
        elif (actorNo == 6):
            if posX < (maxCol-2):
                posX += 1
                updated = True
    ####################################################### 
    ##### Input
    ####################################################### 
    if (updated):
        startY = 0
        endY = posY
        if posY > 0:
            startY = posY-1
        if endY >= maxRow:
            endY = maxRow
        if actorNo == 0:
            endY += 4
        elif actorNo == 1:
            endY += 3
        elif actorNo == 2:
            endY += 3
        elif actorNo == 3:
            endY += 3
        elif actorNo == 4:
            endY += 3
        elif actorNo == 5:
            endY += 3
        else:
            endY += 2
        if endY >= maxRow:
            endY = maxRow
        table(startY,endY)
        draw()
        updated = False

#################################################################
###### main program #############################################
#################################################################
splash()
genTable()
table()
draw()
Render = Timer(1)
Tmr.init( period=1000, mode=Timer.PERIODIC, callback=cbFalling)
Render.init( period=100, mode=Timer.PERIODIC, callback=cbRender)
while swM1.value():
    pass
Tmr.deinit()
Render.deinit()
scr.fill(0)
spi.deinit()

สรุป

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

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

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