跳到主要內容

VBA(1)-用VBA製作貪食蛇

最近突然有個想法,想用VBA做一個貪食蛇的小遊戲,歷經了5天終於把它完成了,製作的過程中,遇到了一些問題,這些問題我們一一的解決它,完成了一個很陽春的貪食蛇,雖然沒有很多附加功能,但也算是個小成果,在這邊分享我的作法給大家。

我們將貪食蛇所需要的功能解構,再將每一個部分建立好,最後拼起來變成一個完整的遊戲,我們將其解構如下:

1.         建立遊戲框架

2.         初始蛇身

3.         隨機產生食物

4.         讓蛇身移動

5.         用鍵盤操控方向

6.         死掉的條件

7.         蛇身移動的速度

8.         吃到食物蛇身變長

我們針對每一個部分進行說明:

1.         建立遊戲框架

我們用很陽春的方式,建立一個30*30的範圍,讓蛇只能在這範圍內移動。

2.         初始蛇身

我們的初始蛇身設定成長度為4,亦可自訂,將蛇頭的部分定位在(15,15)的位置(也就是P16),然後讓蛇一開始往上走,所以蛇身會是(15,15)(15,16)(15,17),(15,18),然後將此四格填黑,形成初始的蛇身。

程式碼:

    Dim startpoi_x%, startpoi_y%

    Cells.Interior.ColorIndex = 0

    startpoi_x = 15: startpoi_y = 15: length = 4: flag = False

    ReDim body_x(length - 1): ReDim body_y(length - 1)

   

    For e = 0 To length - 1

        body_x(e) = startpoi_x + e: body_y(e) = startpoi_y

        Range("A1").Offset(body_x(e), body_y(e)).Interior.Color = vbBlack

    Next e

 3.         隨機產生食物

我們將隨機產生食物的功能寫成另一個Sub,需要產生食物,就呼叫此函數。我們利用隨機產生兩個1~30的整數,分別為列跟行,再針對這個位置將格子填黑,形成隨機產生食物的效果。

程式碼:

    Call Randfood

Sub Randfood()

    Dim Max%, Min%

    Dim flag_generate As Boolean

    Max = 30: Min = 1

    While Not flag_generate

        RandNum_x = Int((Max - Min + 1) * Rnd() + Min)

        RandNum_y = Int((Max - Min + 1) * Rnd() + Min)

        If IsError(Application.Match(RandNum_x, body_x, 0)) Or IsError(Application.Match(RandNum_y, body_y, 0)) Then flag_generate = True

    Wend

    Range("A1").Offset(RandNum_x, RandNum_y).Interior.Color = vbBlack

End Sub

4.         讓蛇身移動

讓蛇身移動的邏輯很簡單,就是將原本的蛇身最後一個身體反白,新的蛇頭填黑,這樣就可以形成蛇身移動的效果。但這邊遇到一個問題,下一個蛇頭如何決定? 所以我們寫個一個函數是更新蛇身的函數,這個函數會給出新的蛇身,根據新的蛇身就可以知道新的蛇頭。

程式碼:

        Range("A1").Offset(body_x(length - 1), body_y(length - 1)).Interior.Color = vbWhite

        body_x = movebody(movepoi_x, body_x, length): body_y = movebody(movepoi_y, body_y, length)

        Range("A1").Offset(body_x(0), body_y(0)).Interior.Color = vbBlack

Function movebody(headpoi As Integer, oldbody As Variant, bodylen As Integer) As Variant

    Dim newbody() As Variant

    ReDim newbody(bodylen - 1)

    newbody(0) = headpoi

    For nb = 0 To bodylen - 2

        newbody(nb + 1) = oldbody(nb)

    Next

    movebody = newbody

End Function

5.         用鍵盤操控方向

要在蛇移動過程中操作鍵盤等同於在程式執行過程中控制鍵盤,這必須在城市中加上Doevents,讓程式執行中可以將控制權轉給作業系統,這時就可以操作鍵盤,另外我們還需要知道我們按下哪個鍵,所以我們在一開始用程式最上層宣告Private Declare PtrSafe Function GetKeyboardState Lib "user32" (pbKeyState As Byte) As Long,並寫一個函數輸出0(),1(),2(),3(),就由輸出值我們知道按下了哪個方向,再呼叫movebody將蛇身更新,此時就可以達到操作蛇身的效果。

程式碼:

    While Not flag

        Range("A1").Select

        Select Case dir

            Case 0

                j = j - 1

            Case 1

                j = j + 1

            Case 2

                i = i - 1

            Case 3

                i = i + 1

        End Select

        movepoi_x = startpoi_x + j: movepoi_y = startpoi_y + i  ‘新的蛇頭

Sub keyboard_dir()

Dim keys(0 To 255) As Byte

GetKeyboardState keys(0)

Select Case dir

    Case 0, 1

        If keys(37) > 127 Then dir = 2 '

        If keys(39) > 127 Then dir = 3 '

    Case 2, 3

        If keys(38) > 127 Then dir = 0 '

        If keys(40) > 127 Then dir = 1 '

End Select

End Sub

6.         死掉的條件

死掉的條件有兩種,第一種撞到自己身體死掉 ; 第二種撞到牆死掉,第一種只要確定新的蛇頭是不是身體的一部分,是的話就等於撞到身體了。第二種只要判斷蛇頭位置有沒有超過邊界就好。

程式碼:

        For m = 0 To length - 1

            If movepoi_x = body_x(m) And movepoi_y = body_y(m) Then MsgBox "Game Over": End

        Next

        If movepoi_x > 30 Or movepoi_x < 1 Or movepoi_y > 30 Or movepoi_y < 1 Then MsgBox "Game Over": End 

7.         蛇身移動的速度

我們用了系統延遲的功能,讓蛇有在移動的效果,如果沒用延遲功能,程式的執行速度很快,會一下子就撞到牆壁,就Game Over了。所以我們在程式最上層宣告了Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal ms As LongPtr),並在程式中加了Sleep(80),其中80是指80毫秒的意思,亦可自訂,值越小速度越快。

8.         吃到食物蛇身變長

新的蛇頭的位置等於食物的位置等同於吃到食物了,此時將蛇身邊長一格,這邊用ReDim Preserve語法保持原蛇身並新增一格,將新增的一格填黑,形成蛇身變長的效果。

程式碼:

If body_x(0) = RandNum_x And body_y(0) = RandNum_y Then

            length = length + 1

            ReDim Preserve body_x(length - 1): ReDim Preserve body_y(length - 1)

            If body_x(length - 2) = body_x(length - 3) And body_y(length - 2) > body_y(length - 3) Then

                body_x(length - 1) = body_x(length - 2): body_y(length - 1) = body_y(length - 2) + 1

            ElseIf body_x(length - 2) = body_x(length - 3) And body_y(length - 2) > body_y(length - 3) Then

                body_x(length - 1) = body_x(length - 2): body_y(length - 1) = body_y(length - 2) - 1

            ElseIf body_x(length - 2) > body_x(length - 3) And body_y(length - 2) = body_y(length - 3) Then

            body_x(length - 1) = body_x(length - 2) + 1: body_y(length - 1) = body_y(length - 2)

            ElseIf body_x(length - 2) < body_x(length - 3) And body_y(length - 2) > body_y(length - 3) Then

            body_x(length - 1) = body_x(length - 2) - 1: body_y(length - 1) = body_y(length - 2)

            End If

            Call Randfood

        End If

好了,以上8個功能拼湊在一起就能成為一個很陽春的貪食蛇遊戲了。提供完整檔案給大家參考。

@VBA-貪食蛇小遊戲

留言

這個網誌中的熱門文章

RPA-Uipath筆記(1) - 如何從網路上下載資料並存至指定路徑

最近在做 RPA 相關專案時遇到了這個問題: 如何從網路上下載資料並存至指定路徑? 針對這個問題我們採取的解決方案是 先讓檔案下載到電腦的預設路徑,再將檔案移動到我們指定的資料夾 。 以下我們用下載政府公開資料平台的資料 ("https://data.gov.tw/dataset/116285") 為例, Uipath 的流程建立如下: 使用到了 Wait for Download 、 Move File 、 Delete File 三個 Activity , Wait for Download 中 Monitored folder 必須放瀏覽器中設定的下載檔案的預設路徑且關閉詢問儲存位置。 Downloaded file 則是設定一個變數名稱 downloadfile( 可自訂 ) ,此變數的資料型態是 FileInfo 。中間則放入下載的流程步驟,從下載開始到結束都會在此 Activity 中執行完畢,執行完後所下載的檔案的相關資訊都會被存放在變數 downloadfile 中。 接下來是將檔案從預設下載路徑中移至指定路徑,這裡就需要下載預設路徑與指定路徑,這兩個我們在流程一開始就建了了兩個變數 dnlo_path 、 asng_path ,寫法如下: dnlo_path = "C:\Users\" +system.Environment.UserName+ "\Downloads" system.Environment.UserName à VB 語法,代表目前電腦的使用者名稱。 asng_path = "D:\Bolg 資料 \Uipath\Uipath(1)" Move File Activity 中 From 要放入目前檔案的位置; To 則要放入檔案存放的指定的位置,但因為是要做檔案的移動,所以以上兩個路徑後面都要再加上檔案名稱,這時可以使用 downloadfile 這個變數的屬性值叫出檔案名稱( downloadfile.Name ),當檔案名稱是隨機變動的時候,非常好用。另外,特別值得注意的是, To 的部分在加上檔案名稱時,可以不用用原始檔案的名稱,這時可以同時達到修改檔名的效果。 Overwrite 的部分打勾代表再重複執行一次的時...

RPA-Uipath(2) - 字串分割(String.Split())與合併(String.join())

在資料清理中,我們常常需要對字串進行分割或合併,今天來簡單介紹 Uipath 中字串如何進行分割與合併。 1.      字串分隔 ( 單一分割符號 ) 我們首先建了一個字串變數 Str1= "Jimmy,Johnson,Shawn,Alan,Nick" ,我們現在想把這些人名一個一個切割出來,很明顯只要將 ”,” 作為分個符號就可以了。這邊我們建立一個陣列變數 Str1_Array=Str1.split(","c) ,這個陣列變數就會儲存分割出來的結果。我們接下來用 For each 與 Write Line 將陣列的每一項內容印出來,就可以知道我們分割的結果有沒有成功。 2.      字串分隔 ( 多個分割符號 ) 我們首先建了一個字串變數 Str2= "Jimmy,Johnson.Shawn Alan,Nick" ,我們現在想把這些人名一個一個切割出來,很明顯要將 ”,” 、 ”.” 、 ” “ 都作為分個符號才能完整分割。這邊我們建立一個陣列變數 Str2_Array= Str2.Split(",|.| ".ToArray()) or Str2.Split({","c,"."c," "c}) ,這個陣列變數就會儲存分割出來的結果。我們接下來用 For each 與 Write Line 將陣列的每一項內容印出來,就可以知道我們分割的結果有沒有成功。 3       字串的合併 (Array 字串元素合併 ) 我們首先建了一個字串變數 Str3_Array= {"John","Jimmy","Shawn","Alan"} ,我們現在想把這些人名中間用 ”|” 符號隔開並且變成一個字串,這裡我們是用 String.Join( 連接符號 , 陣列 ) 這個函數達到這個目的。我們建立一個變數 Str3_Join ,使 Str3_Join=String.Join("|", Str3_Array) ,此時 Str3_Join 的結果就會是 John|Jimmy|Shawn|Alan 。我們接下來用 Write...

RPA-Uipath(5)-如何讀取Mail與取得Mail的資訊

本篇主要介紹 Uipath 取得 mail 相關資訊與附件的方法,因為 mail 的種類眾多,本範例以 Gmail 為例。首先在使用 Uipath 取得 Gmail 相關資訊前須對 Google 帳戶的安全性進行兩階段驗證設定,設定方式如下: Step1. 點選 ” 管理你的 Google 帳戶 ” Step2.  點選 ” 安全性 ” à ” 兩階段驗證 ” à 點選 ” 開始使用 ” Step3.  依 Google 所提示步驟,您最後會取得一組密碼,請務必將此密碼記下。 接下來要開始使用 Uipath 擷取 mail 相關資訊,一開始我們先使用 Get Password 儲存 mail 登入的密碼,此時密碼不是使用您真正的密碼而是使用剛剛兩階段驗證後取得的密碼,並儲存於變數 password 。 接下來使用 Get IMAP Mail Messages 來取得 mail 的相關資訊,在使用 Get IMAP Mail Messages 時必須在其屬性設定區塊進行一些參數設定。 Port 欄位填入 993 , Server 欄位填入 "imap.gmail.com" , Email 欄位填入您的 Email , Password 欄位填入 變數 password , Message 欄位我們用 MailMessage 變數填入 Mail 的相關資訊都會存在 MailMessage 變數中,我們可以用 For Each 迴圈逐一取得 mail 的相關資訊。 For Each 的屬性區塊部分也需要進行一些參數設定, List of item 欄位需填入 MailMessage 變數, TypeArgument 則須設定為 System.Net.Mail.MailMessage 。 設定完成後,我們先用 Write Line 將信件的主旨印出做為測試( 這邊我們只印出前 5 封信件的主旨,可在 Get IMAP Mail Messages 的屬性區塊中 Top 欄位中設定 ),如果要印出信件主旨,需要用 item.Subject 屬性,如要印出其他資訊,我們將常用的幾個列在以下表格: 最後,有些信件會附帶一些附件,我們如何將這些物件儲存下來呢 ? 這邊只需要用到 Save Attachments 即可。我們將其放在迴圈內以下載每個有附件的 mail...