用EXCEL编写俄罗斯方块小游戏(基于VBA)

Posted wrwttsy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用EXCEL编写俄罗斯方块小游戏(基于VBA)相关的知识,希望对你有一定的参考价值。

用EXCEL编写俄罗斯方块小游戏(基于VBA)


工作属性原因,工作中使用excel办公是常态。前一阵子因工作业务需求,需要用到VBA。研究了一阵子VBA,解决了当时的需求。
后来想想,VBA可以如此彻底的控制excel,那么可不可以编个小游戏呢。
说干就干,先拿与表格最像的俄罗斯方块试试手。

预览成品效果 (文末附下载地址┗( ▔, ▔ )┛)

第一步:准备工作

首先,俄罗斯方块游戏需要完成哪些工作。

  1. 设置游戏窗口大小:俄罗斯方块游戏窗口大小为横10个方格、竖20个方格。
  2. 设置可变形方块元素:俄罗斯方块一共7种不同样式的方块。
  3. 设置游戏交互:俄罗斯方块有4种操作:,左移方块、右移方块、方块加速下落、方块变形。
  4. 保持游戏正常进行:随机形状方块下落,至底部或遇到方块后停止。任意行方块满行则分数+100,此行消除。方块堆满窗口游戏结束。

第二步:分步解决

(一)设置游戏窗口

设置游戏窗口大小及外观,对于有着多年做表经验的我来说,简直是信手拈来。(原来编游戏如此简单,这么快就完成了第一步。休息一天O(∩_∩)O哈哈~)

(二)初始化游戏各对象

设计思路:标准的俄罗斯方块共有7个方块,分别是“一”、“J”、“L”、“T”、“S”、“Z”、“田”。
我们注意到每个不同形状的俄罗斯方块均有4个方格,我们选取其中一个作为形状的旋转中心,并通过相对中心的偏移坐标储存不同方块。

	shape_0 = Array(Array(0, 0), Array(0, 1), Array(0, -1), Array(0, 2)) '初始化长方块
    shape_1 = Array(Array(0, 0), Array(0, 1), Array(0, -1), Array(-1, 1)) '初始化L1方块
    shape_2 = Array(Array(0, 0), Array(0, 1), Array(0, -1), Array(-1, -1)) '初始化L2方块
    shape_3 = Array(Array(0, 0), Array(0, 1), Array(0, -1), Array(-1, 0)) '初始化T方块
    shape_4 = Array(Array(0, 0), Array(0, 1), Array(-1, -1), Array(-1, 0)) '初始化Z方块
    shape_5 = Array(Array(0, 0), Array(0, -1), Array(-1, 1), Array(-1, 0)) '初始化S方块
    shape_6 = Array(Array(0, 0), Array(-1, 0), Array(-1, -1), Array(0, -1)) '初始化田方块
    shape_base = Array(shape_0, shape_1, shape_2, shape_3, shape_4, shape_5, shape_6) '所有方块数据存入数组

通过数组嵌套(非三维数组)完成方块坐标数据的存储。
随机产生0–6的随机数,根据随机数选取方块坐标数据。以焦点坐标为中心利用Offset函数偏移出四个range单元格,使用Union函数连接四个range单元格生成。然后对方块着色并加边框。 并对当前方块的下壁碰撞值赋值(用于碰撞检测,判定方块是否到底或者已落至某一方块上方)。

Sub draw_shape(s_n_can, s_s_x, s_s_y)'画出随机方块过程   
    Set drop_rng_focus = Cells(s_s_x, s_s_y) '传入焦点方块X,Y
    Set b_rng_can = Cells(s_s_x, s_s_y)
    Set p_rng_can = b_rng_can
    For dr_i_2 = 0 To UBound(shape_base(s_n_can))
        off_x_can = shape_base(s_n_can)(dr_i_2)(0)
        off_y_can = shape_base(s_n_can)(dr_i_2)(1)
        Set p_rng_can = Union(p_rng_can, b_rng_can.Offset(off_x_can, off_y_can))'偏移并连接单元格
    Next
    p_rng_can.Interior.ColorIndex = shape_color(s_n_can)'对当前方块加底色(底色数据存在一维数组shape_color中)
    p_rng_can.Borders.LineStyle = 1'对当前方块加边框
    
    Set drop_rng = p_rng_can  '下落方块赋地址
    For Each cel In drop_rng
        If s_arr_top(cel.Column) < cel.Row Then s_arr_top(cel.Column) = cel.Row '下落图块碰撞下壁刷新赋值
    Next
End Sub

根据游戏机制,在出生点Cells(s_s_x, s_s_y)生成并画出方块后,方块需要按照一定速度下降。
通过for循环+系统休眠方式实现方块缓慢地不停下落。方块每下落一层,休眠seep_speed时间。可以通过初始化或赋值seep_speed值来控制方块下落速度。

注:这里作者放弃使用EXCEL中VBA自带的OnTime方法,而是通过【for循环+系统休眠】方式实现方块缓慢地不停下落。
	Application对象的OnTime方法能够安排一个过程在将来的特定时间运行,作用是安排某个过程的自动运行。
	但是OnTime方法有个致命的缺点就是最小运行时间间隔为1秒钟,对于俄罗斯方块游戏来说每1秒钟下落一层,太过缓慢,且无法调节下落速度,不灵活。

方块下落至底部或落至底部累积方块上之后,使用Union函数将当前下落方块与底部累积块合并,生成新的底部累积方块range。

Sub draw_shape_down(s_n_can, s_s_x, s_s_y)
    draw_shape s_n_can, s_s_x, s_s_y '生成下落方块第一帧range,并保存下落方块range至drop_rng及下落方块焦点range至drop_rng_focus。
    drop_rng_col = s_n_can '保存下落块索引
    sleep (seep_speed) '延时
    tt_down = 18 '设置最大下降步数
     For ii = 1 To tt_down
        If pz_check() = 1 Then  '若上下碰撞壁有重叠,即下降方块已经叠放在某个方块之上
            Exit For
        End If
        drop_rng.Interior.ColorIndex = 0 '将上一步方块颜色释放
        drop_rng.Borders.LineStyle = 0 '将上一步方块边框释放
        Set drop_rng = drop_rng.Offset(1, 0) '将方块range下移一步
        Set drop_rng_focus = drop_rng_focus.Offset(1, 0) '将方块焦点range下移一步
        drop_rng.Interior.ColorIndex = shape_color(s_n_can) '对当前方块着色
        drop_rng.Borders.LineStyle = 1 '对当前方块加边框
        For Each cel In drop_rng
            If s_arr_top(cel.Column) < cel.Row Then s_arr_top(cel.Column) = cel.Row '下落图块碰撞下壁刷新赋值
        Next
        sleep (seep_speed) '延时
     Next
    If foot_shape_rng Is Nothing Then '检查此时是否有底部累积图块,若没有则等于当前下落方块
        Set foot_shape_rng = drop_rng
    End If
    Set foot_shape_rng = Union(foot_shape_rng, drop_rng) '底部累积图块更新range
    foot_shape_rng.Borders.LineStyle = 1 '底部累积图块加边框
    For Each cel_foot In foot_shape_rng
        If s_arr_foot(cel_foot.Column) > cel_foot.Row Then s_arr_foot(cel_foot.Column) = cel_foot.Row  '底部累积图块碰上撞壁数组刷新赋值
    Next
    Set drop_rng = Nothing
    Call goal_disshape '检测是否得分
End Sub

(三)游戏交互

设计思路:通过调用windows的API(GetKeyboardState)来监听键盘操作以完成交互。

注:这里作者放弃使用EXCEL中VBA自带的Onkey事件,而是调用windows的API(GetKeyboardState)来监听键盘操作以完成交互。
	Onkey方法能够监听到我们按下的是计算机上的那个按键,并能够根据特定的按键执行特定的代码的能力。
	但是Onkey方法有个致命的缺点就是Onkey方法在程序执行sleep (seep_speed) 休眠时,无响应。也就是说方块下落时按键无响应。

调用windows的API需要在游戏执行页表(Sheet)下书写代码。

Private Declare Function GetKeyboardState Lib "user32" (pbKeyState As Byte) As Long
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    If kong_ou Mod 2 = 1 Then    
        Dim keycode(0 To 255) As Byte
        GetKeyboardState keycode(0)
        If keycode(38) > 127 Then   '上
            Call turn_shape  '调用方块变形过程(函数)
        ElseIf keycode(39) > 127 Then   '右
            Call move_right  '调用方块右移过程(函数)
        ElseIf keycode(40) > 127 Then '下
            Call move_down   '调用方块快速下降过程(函数)。通过赋值减小seep_speed值来实现。
        ElseIf keycode(37) > 127 Then '左
            Call move_left  '调用方块左移过程(函数)
        End If
    End If
    kong_ou = kong_ou + 1
    [l25].Select  '更改页面样式时请将本行代码注释掉,否则无法修改。修改页面完成后请取消注释。
End Sub

同时为了防止按方向键时选中单元格变化影响游戏体验,加入判断机制,固定选中单元格为[l25]。以免出现下图现象,影响体验。

(四)保持游戏正常运行

  1. 碰撞检测实现。
    设计思路:
    用一维数组s_arr_top()动态存储正在下落方块的底部单元格的行数row。
    用一维数组s_arr_foot()动态存储底部累积单元格最上层单元格的行数row。
    通过预测下移一层后数组数据有无重复数据判断是方块否可以继续下落。
If dw_ou = 0 Then
   p_rng_can.Interior.ColorIndex = shape_color(s_n_can)
   p_rng_can.Borders.LineStyle = 1
   Set drop_rng = p_rng_can  '下落方块赋地址
      For Each cel In drop_rng
          If s_arr_top(cel.Column) < cel.Row Then s_arr_top(cel.Column) = cel.Row '下落图块碰撞下壁刷新赋值
      Next
End If
Function pz_check() '通过下落方块底部边界数组与底部累积方块顶部边界数组同位比对,判断下降方块是否已经置于底部方块以上,若位于某个方块上方了,则返回检测结果1
    ck_zan = 0
    For ck_i = sp_y_bsc - 4 To sp_y_bsc + 5
        If s_arr_top(ck_i) + 1 >= s_arr_foot(ck_i) Then ck_zan = 1
        If s_arr_foot(ck_i) = sp_x_bsc + 1 Then game_over_yn = 1
    Next
    pz_check = ck_zan
End Function
  1. 是否可以移动或变形检测。
    移动检测:检测方块range是否超出游戏窗口左右边界或与底部累积块有重叠。
Function move_lim(lim_rng As Range) '方块是否能够移动检测,返回值为0则可以移动,返回值为1则不可以移动
    move_lim = 0
    For Each lim_cell In lim_rng
        If lim_cell.Column < sp_y_bsc - 4 Or lim_cell.Column > sp_y_bsc + 5 Then
            move_lim = 1
        End If
    Next
    Dim mix_rng As Range
    If Not foot_shape_rng Is Nothing Then
        Set mix_rng = Intersect(lim_rng, foot_shape_rng) '截取重叠部分,检验是否有重叠
        If Not mix_rng Is Nothing Then
            move_lim = 1
        End If
    End If
End Function

变形检测:检测变形后方块range是否超出游戏窗口左右边界,若超出则平移回游戏界面内。

    If change_ou = 1 Then  '进入判断是否超过左右边界,若超过左右边界,则平移至界内
        '变形后出界纠正操作开始
        bound_fin = sp_y_bsc
        For Each bound_cell In p_rng_can
            If bound_cell.Column < sp_y_bsc - 4 Then
                If bound_cell.Column < bound_fin Then
                    bound_fin = bound_cell.Column
                End If
            End If
            If bound_cell.Column > sp_y_bsc + 5 Then
                If bound_cell.Column > bound_fin Then
                    bound_fin = bound_cell.Column
                End If
            End If
        Next
        If bound_fin < sp_y_bsc - 4 Then
            Set p_rng_can = p_rng_can.Offset(0, sp_y_bsc - 4 - bound_fin)
        End If
        If bound_fin > sp_y_bsc + 5 Then
            Set p_rng_can = p_rng_can.Offset(0, sp_y_bsc + 5 - bound_fin)
        End If
         '变形后出界纠正操作结束
    End If
  1. 得分判定及消除。
    设计思路:每次方块掉落结束与下次方块掉落间隙。对游戏窗口进行一次自上而下,自左至右的遍历,判断某行是否已经塞满方块。
    若某行塞满,则分数+100。
    将游戏界面以集齐一行的单元格行为界,分为上下两部分(不含满单元格行)。分别与底部累积块foot_shape_rng通过Intersect函数截取重叠部分,形成不含满行块的上半部分累积块range为dis_foot_rng_up,下半部分累积块range为dis_foot_rng_down。上半部分累积块range整体下移1行并与下半部分累积块range组合,形成新的底部累积块。
Sub goal_disshape() '得分检测、消除满行、上部方块下移一行过程
    game_s_x = sp_x_bsc - 1 '定位游戏界面左上角焦点X
    game_s_y = sp_y_bsc - 4 '定位游戏界面左上角焦点Y
    For goal_i = 0 To 19  '游戏区域内循环遍历
        dis_all_line = 0
        Set dis_range = Range(Cells(game_s_x + goal_i, game_s_y), Cells(game_s_x + goal_i, game_s_y + 9)) '游戏界面内第N行range行内遍历
        For Each ran_dis In dis_range  '第N行range行内遍历
            dis_all_line = dis_all_line + 1 '单行左起累积个
            If ran_dis.Interior.ColorIndex < 0 Then '一旦遇到非着色块,立即跳出本行循环
                dis_all_line = -1
                Exit For
            End If
        Next
        
        If dis_all_line = 10 Then  '判断行内有底色单元格个数,若为10怎说明已经集齐一行积木
          
          '''将游戏界面以集齐一行的单元格行为界,分为上下两部分(不含满单元格行)。分别与底部累积块foot_shape_rng通过Intersect函数截取重叠部分,形成不含满行块的上半部分累积块range为dis_foot_rng_up,下半部分累积块range为dis_foot_rng_down。
            Set dis_up_ran = Range(Cells(game_s_x, game_s_y), Cells(game_s_x + goal_i - 1, game_s_y + 9))
                Set dis_foot_rng_up = Intersect(dis_up_ran, foot_shape_rng).Offset(1, 0) '截取重叠部分并整体下移1行形成上半部分累积块
            If goal_i = 19 Then
                 Set dis_foot_rng_down = Nothing  '满行单元格处于最后一行,则下半部分累积块为空
            Else
                Set dis_down_ran = Range(Cells(game_s_x + goal_i + 1, game_s_y), Cells(game_s_x + 19, game_s_y + 9))
                Set dis_foot_rng_down = Intersect(dis_down_ran, foot_shape_rng) '截取重叠部分,形成下半部分累积块
            End If
            
           ''''''''''''''''分情况重组底部累积块
            If dis_foot_rng_down Is Nothing Then
                Set foot_shape_rng = dis_foot_rng_up '若下半部分累积块为空,则新底部累积块等于上半部分累积块。
            Else
                Set foot_shape_rng = Union(dis_foot_rng_up, dis_foot_rng_down) '正常情况下,新底部累积块等于上半部分累积块合并下半部分累积块。
            End If
                     
            ''''''''''''重新赋值底部累积块碰撞壁上沿
            For s_arr_foot_i = sp_y_bsc - 4 To sp_y_bsc + 5
                s_arr_foot(s_arr_foot_i) = sp_x_bsc + 19 '先统一设置上沿为界面底沿
            Next
            For Each cel_foot In foot_shape_rng
                If s_arr_foot(cel_foot.Column) > cel_foot.Row Then s_arr_foot(cel_foot.Column) = cel_foot.Row  '底部累积图块碰上撞壁数组刷新赋值
            Next
            
            
             ''''''''''''消除满行特效并分数加100
            Set texiao_rng = Range(Cells(game_s_x + goal_i, game_s_y), Cells(game_s_x + goal_i, game_s_y + 9))
                shan_ii = 3
                For texiao_i = 3 To 8
                    sleep (0.1)
                    If shan_ii = 3 Then
                        shan_ii = 0
                    Else
                        shan_ii = 3
                    End If
                    texiao_rng.Interior.ColorIndex = shan_ii
                Next
                For Each tx_rng In texiao_rng
                    sleep (0.05) '延时
                    tx_rng.Interior.ColorIndex = 0
                Next
                sleep (seep_speed) '延时
            score = score + 100 '分数+100
            score_win.Value = score   '外显分数
       
           
             ''''''''''''消除行以上的单元格下移
            dis_up_ran.Copy    '复制消除行上部分方块集合
            dis_up_ran.Offset(1, 0).PasteSpecial   '下移一行粘贴
            Range(Cells(game_s_x, game_s_y + 9), Cells(game_s_x + 1, game_s_y)).Clear '擦除最顶行
        End If
    Next
End Sub
  1. 其他
    ①全局变量及初始化配置
Private game_over_yn As Variant     '定义 游戏是否可以执行
Private game_win As Range           '定义 游戏显示窗口位置
Private next_shape_win As Range     '定义 下一方块显示窗口位置
Private score_win As Range          '定义 成绩显示窗口位置
Private shape_base As Variant       '定义 方块形状数组
Private shape_color As Variant      '定义 方块色彩数组
Private foot_shape_rng As Range     '定义 底部累计方块的range
Private drop_whic_focus_next As Variant  '定义 下一次掉落方块的随机种类
Private drop_whic_focus As Variant  '定义 正在移动方块的随机种类
Private sp_x_bsc As Variant         '定义 正在移动方块出生点X
Private sp_y_bsc As Variant         '定义 正在移动方块出生点Y
Private seep_speed As Variant       '定义速度
Private drop_rng As Range           '定义 正在移动方块
Private drop_rng_focus As Range     '定义 正在移动方块旋转焦点
Private drop_rng_temp As Range      '定义 正在被操作方块
Private drop_rng_col As Variant     '定义 正在被操作方块颜色指针
Private s_arr_top(8 To 17)          '定义 移动方块各列最低端
Private s_arr_foot(8 To 17)         '定义底部累积方块各列最高端
Private score As Variant            '定义得分
Public kong_ou As Variant           '定义控制奇偶数
Public change_ou As Variant         '定义变形校验奇偶数

Sub overall_situ_config() '初始化全局配置   
    Set next_shape_win = Range("t3:w6")  '初始化下一方块显示窗口位置
    Set game_win = Range("h3:q22")       '初始化游戏显示窗口位置
    Set score_win = [t9]                 '初始化 成绩显示窗口位置
    score = 0       '初始化分数为0分
    kong_ou = 1      '初始化隔次执行间隔器
    change_ou = 0    '初始化改变形状校验值
    score_win.Value = score '外显分数0
    shape_color = Array(3, 4, 5, 10, 7, 28, 45) '初始化方块底色
    sp_x_bsc = 4     '初始掉落焦点坐标X
    sp_y_bsc = 12    '初始掉落焦点坐标Y
End Sub

Sub init_config() '初始化单次掉落数据    
    shape_base = Array(shape_0, shape_1, shape_2, shape_3, shape_4, shape_5, shape_6) '所有方块数据存入数组
    '''''''初始化本次掉落方块
    If IsEmpty(drop_whic_focus) Then
        Randomize '重置随机数种子
        drop_whic_focus = Int(0 + (6 - 0 + 1) * Rnd()) '产生随机数0-6
    Else
        drop_whic_focus = drop_whic_focus_next
    End If
    '''''''生成下次掉落方块
    Randomize '重置随机数种子
    drop_whic_focus_next = Int(0 + (6 - 0 + 1) * Rnd()) '产生随机数0-6
    s_next_x = sp_x_bsc
    s_next_y = sp_y_bsc + 9
    draw_next drop_whic_focus_next, s_next_x, s_next_y  '调用画下一掉落方块过程
    seep_speed = 0.5  '初始化速度
    For

用Shell编写的俄罗斯方块代码

                          用Shell编写的俄罗斯方块代码

  不得不承认任何一门语言玩6了,啥都能搞出来啊,竟然用Shell编写出来了一个俄罗斯方块游戏的代码,很有意思,这个代码不是我写出来的,不过大家可以下载一下在windows或是linux上无聊时玩耍一下,当然也可以改改里面的代码,有助于你学习Shell编程哟~

  1 #!/bin/bash
  2  
  3 # Tetris Game
  4 # 10.21.2003 xhchen<[email][email protected][/email]>
  5  
  6 #APP declaration
  7 APP_NAME="${0##*[\\/]}"
  8 APP_VERSION="1.0"
  9  
 10  
 11 #颜色定义
 12 cRed=1
 13 cGreen=2
 14 cYellow=3
 15 cBlue=4
 16 cFuchsia=5
 17 cCyan=6
 18 cWhite=7
 19 colorTable=($cRed $cGreen $cYellow $cBlue $cFuchsia $cCyan $cWhite)
 20  
 21 #位置和大小
 22 iLeft=3
 23 iTop=2
 24 ((iTrayLeft = iLeft + 2))
 25 ((iTrayTop = iTop + 1))
 26 ((iTrayWidth = 10))
 27 ((iTrayHeight = 15))
 28  
 29 #颜色设置
 30 cBorder=$cGreen
 31 cScore=$cFuchsia
 32 cScoreValue=$cCyan
 33  
 34 #控制信号
 35 #改游戏使用两个进程,一个用于接收输入,一个用于游戏流程和显示界面;
 36 #当前者接收到上下左右等按键时,通过向后者发送signal的方式通知后者。
 37 sigRotate=25
 38 sigLeft=26
 39 sigRight=27
 40 sigDown=28
 41 sigAllDown=29
 42 sigExit=30
 43  
 44 #七中不同的方块的定义
 45 #通过旋转,每种方块的显示的样式可能有几种
 46 box0=(0 0 0 1 1 0 1 1)
 47 box1=(0 2 1 2 2 2 3 2 1 0 1 1 1 2 1 3)
 48 box2=(0 0 0 1 1 1 1 2 0 1 1 0 1 1 2 0)
 49 box3=(0 1 0 2 1 0 1 1 0 0 1 0 1 1 2 1)
 50 box4=(0 1 0 2 1 1 2 1 1 0 1 1 1 2 2 2 0 1 1 1 2 0 2 1 0 0 1 0 1 1 1 2)
 51 box5=(0 1 1 1 2 1 2 2 1 0 1 1 1 2 2 0 0 0 0 1 1 1 2 1 0 2 1 0 1 1 1 2)
 52 box6=(0 1 1 1 1 2 2 1 1 0 1 1 1 2 2 1 0 1 1 0 1 1 2 1 0 1 1 0 1 1 1 2)
 53 #所有其中方块的定义都放到box变量中
 54 box=(${box0[@]} ${box1[@]} ${box2[@]} ${box3[@]} ${box4[@]} ${box5[@]} ${box6[@]})
 55 #各种方块旋转后可能的样式数目
 56 countBox=(1 2 2 2 4 4 4)
 57 #各种方块再box数组中的偏移
 58 offsetBox=(0 1 3 5 7 11 15)
 59  
 60 #每提高一个速度级需要积累的分数
 61 iScoreEachLevel=50        #be greater than 7
 62  
 63 #运行时数据
 64 sig=0                #接收到的signal
 65 iScore=0        #总分
 66 iLevel=0        #速度级
 67 boxNew=()        #新下落的方块的位置定义
 68 cBoxNew=0        #新下落的方块的颜色
 69 iBoxNewType=0        #新下落的方块的种类
 70 iBoxNewRotate=0        #新下落的方块的旋转角度
 71 boxCur=()        #当前方块的位置定义
 72 cBoxCur=0        #当前方块的颜色
 73 iBoxCurType=0        #当前方块的种类
 74 iBoxCurRotate=0        #当前方块的旋转角度
 75 boxCurX=-1        #当前方块的x坐标位置
 76 boxCurY=-1        #当前方块的y坐标位置
 77 iMap=()                #背景方块图表
 78  
 79 #初始化所有背景方块为-1, 表示没有方块
 80 for ((i = 0; i < iTrayHeight * iTrayWidth; i++)); do iMap[$i]=-1; done
 81  
 82  
 83 #接收输入的进程的主函数
 84 function RunAsKeyReceiver()
 85 {
 86         local pidDisplayer key aKey sig cESC sTTY
 87  
 88         pidDisplayer=$1
 89         aKey=(0 0 0)
 90  
 91         cESC=`echo -ne "\033"`
 92         cSpace=`echo -ne "\040"`
 93  
 94         #保存终端属性。在read -s读取终端键时,终端的属性会被暂时改变。
 95         #如果在read -s时程序被不幸杀掉,可能会导致终端混乱,
 96         #需要在程序退出时恢复终端属性。
 97         sTTY=`stty -g`
 98  
 99         #捕捉退出信号
100         trap "MyExit;" INT TERM
101         trap "MyExitNoSub;" $sigExit
102  
103         #隐藏光标
104         echo -ne "\033[?25l"
105  
106  
107         while :
108         do
109                 #读取输入。注-s不回显,-n读到一个字符立即返回
110                 read -s -n 1 key
111  
112                 aKey[0]=${aKey[1]}
113                 aKey[1]=${aKey[2]}
114                 aKey[2]=$key
115                 sig=0
116  
117                 #判断输入了何种键
118                 if [[ $key == $cESC && ${aKey[1]} == $cESC ]]
119                 then
120                         #ESC键
121                         MyExit
122                 elif [[ ${aKey[0]} == $cESC && ${aKey[1]} == "[" ]]
123                 then
124                         if [[ $key == "A" ]]; then sig=$sigRotate        #<向上键>
125                         elif [[ $key == "B" ]]; then sig=$sigDown        #<向下键>
126                         elif [[ $key == "D" ]]; then sig=$sigLeft        #<向左键>
127                         elif [[ $key == "C" ]]; then sig=$sigRight        #<向右键>
128                         fi
129                 elif [[ $key == "W" || $key == "w" ]]; then sig=$sigRotate        #W, w
130                 elif [[ $key == "S" || $key == "s" ]]; then sig=$sigDown        #S, s
131                 elif [[ $key == "A" || $key == "a" ]]; then sig=$sigLeft        #A, a
132                 elif [[ $key == "D" || $key == "d" ]]; then sig=$sigRight        #D, d
133                 elif [[ "[$key]" == "[]" ]]; then sig=$sigAllDown        #空格键
134                 elif [[ $key == "Q" || $key == "q" ]]                        #Q, q
135                 then
136                         MyExit
137                 fi
138  
139                 if [[ $sig != 0 ]]
140                 then
141                         #向另一进程发送消息
142                         kill -$sig $pidDisplayer
143                 fi
144         done
145 }
146  
147 #退出前的恢复
148 function MyExitNoSub()
149 {
150         local y
151  
152         #恢复终端属性
153         stty $sTTY
154         ((y = iTop + iTrayHeight + 4))
155  
156         #显示光标
157         echo -e "\033[?25h\033[${y};0H"
158         exit
159 }
160  
161  
162 function MyExit()
163 {
164         #通知显示进程需要退出
165         kill -$sigExit $pidDisplayer
166  
167         MyExitNoSub
168 }
169  
170  
171 #处理显示和游戏流程的主函数
172 function RunAsDisplayer()
173 {
174         local sigThis
175         InitDraw
176  
177         #挂载各种信号的处理函数
178         trap "sig=$sigRotate;" $sigRotate
179         trap "sig=$sigLeft;" $sigLeft
180         trap "sig=$sigRight;" $sigRight
181         trap "sig=$sigDown;" $sigDown
182         trap "sig=$sigAllDown;" $sigAllDown
183         trap "ShowExit;" $sigExit
184  
185         while :
186         do
187                 #根据当前的速度级iLevel不同,设定相应的循环的次数
188                 for ((i = 0; i < 21 - iLevel; i++))
189                 do
190                         sleep 0.02
191                         sigThis=$sig
192                         sig=0
193  
194                         #根据sig变量判断是否接受到相应的信号
195                         if ((sigThis == sigRotate)); then BoxRotate;        #旋转
196                         elif ((sigThis == sigLeft)); then BoxLeft;        #左移一列
197                         elif ((sigThis == sigRight)); then BoxRight;        #右移一列
198                         elif ((sigThis == sigDown)); then BoxDown;        #下落一行
199                         elif ((sigThis == sigAllDown)); then BoxAllDown;        #下落到底
200                         fi
201                 done
202                 #kill -$sigDown $$
203                 BoxDown        #下落一行
204         done
205 }
206  
207  
208 #BoxMove(y, x), 测试是否可以把移动中的方块移到(x, y)的位置, 返回0则可以, 1不可以
209 function BoxMove()
210 {
211         local j i x y xTest yTest
212         yTest=$1
213         xTest=$2
214         for ((j = 0; j < 8; j += 2))
215         do
216                 ((i = j + 1))
217                 ((y = ${boxCur[$j]} + yTest))
218                 ((x = ${boxCur[$i]} + xTest))
219                 if (( y < 0 || y >= iTrayHeight || x < 0 || x >= iTrayWidth))
220                 then
221                         #撞到墙壁了
222                         return 1
223                 fi
224                 if ((${iMap[y * iTrayWidth + x]} != -1 ))
225                 then
226                         #撞到其他已经存在的方块了
227                         return 1
228                 fi
229         done
230         return 0;
231 }
232  
233  
234 #将当前移动中的方块放到背景方块中去,
235 #并计算新的分数和速度级。(即一次方块落到底部)
236 function Box2Map()
237 {
238         local j i x y xp yp line
239  
240         #将当前移动中的方块放到背景方块中去
241         for ((j = 0; j < 8; j += 2))
242         do
243                 ((i = j + 1))
244                 ((y = ${boxCur[$j]} + boxCurY))
245                 ((x = ${boxCur[$i]} + boxCurX))
246                 ((i = y * iTrayWidth + x))
247                 iMap[$i]=$cBoxCur
248         done
249  
250         #消去可被消去的行
251         line=0
252         for ((j = 0; j < iTrayWidth * iTrayHeight; j += iTrayWidth))
253         do
254                 for ((i = j + iTrayWidth - 1; i >= j; i--))
255                 do
256                         if ((${iMap[$i]} == -1)); then break; fi
257                 done
258                 if ((i >= j)); then continue; fi
259  
260                 ((line++))
261                 for ((i = j - 1; i >= 0; i--))
262                 do
263                         ((x = i + iTrayWidth))
264                         iMap[$x]=${iMap[$i]}
265                 done
266                 for ((i = 0; i < iTrayWidth; i++))
267                 do
268                         iMap[$i]=-1
269                 done
270         done
271  
272         if ((line == 0)); then return; fi
273  
274         #根据消去的行数line计算分数和速度级
275         ((x = iLeft + iTrayWidth * 2 + 7))
276         ((y = iTop + 11))
277         ((iScore += line * 2 - 1))
278         #显示新的分数
279         echo -ne "\033[1m\033[3${cScoreValue}m\033[${y};${x}H${iScore}         "
280         if ((iScore % iScoreEachLevel < line * 2 - 1))
281         then
282                 if ((iLevel < 20))
283                 then
284                         ((iLevel++))
285                         ((y = iTop + 14))
286                         #显示新的速度级
287                         echo -ne "\033[3${cScoreValue}m\033[${y};${x}H${iLevel}        "
288                 fi
289         fi
290         echo -ne "\033[0m"
291  
292  
293         #重新显示背景方块
294         for ((y = 0; y < iTrayHeight; y++))
295         do
296                 ((yp = y + iTrayTop + 1))
297                 ((xp = iTrayLeft + 1))
298                 ((i = y * iTrayWidth))
299                 echo -ne "\033[${yp};${xp}H"
300                 for ((x = 0; x < iTrayWidth; x++))
301                 do
302                         ((j = i + x))
303                         if ((${iMap[$j]} == -1))
304                         then
305                                 echo -ne "  "
306                         else
307                                 echo -ne "\033[1m\033[7m\033[3${iMap[$j]}m\033[4${iMap[$j]}m[]\033[0m"
308                         fi
309                 done
310         done
311 }
312  
313  
314 #下落一行
315 function BoxDown()
316 {
317         local y s
318         ((y = boxCurY + 1))        #新的y坐标
319         if BoxMove $y $boxCurX        #测试是否可以下落一行
320         then
321                 s="`DrawCurBox 0`"        #将旧的方块抹去
322                 ((boxCurY = y))
323                 s="$s`DrawCurBox 1`"        #显示新的下落后方块
324                 echo -ne $s
325         else
326                 #走到这儿, 如果不能下落了
327                 Box2Map                #将当前移动中的方块贴到背景方块中
328                 RandomBox        #产生新的方块
329         fi
330 }
331  
332 #左移一列
333 function BoxLeft()
334 {
335         local x s
336         ((x = boxCurX - 1))
337         if BoxMove $boxCurY $x
338         then
339                 s=`DrawCurBox 0`
340                 ((boxCurX = x))
341                 s=$s`DrawCurBox 1`
342                 echo -ne $s
343         fi
344 }
345  
346 #右移一列
347 function BoxRight()
348 {
349         local x s
350         ((x = boxCurX + 1))
351         if BoxMove $boxCurY $x
352         then
353                 s=`DrawCurBox 0`
354                 ((boxCurX = x))
355                 s=$s`DrawCurBox 1`
356                 echo -ne $s
357         fi
358 }
359  
360  
361 #下落到底
362 function BoxAllDown()
363 {
364         local k j i x y iDown s
365         iDown=$iTrayHeight
366  
367         #计算一共需要下落多少行
368         for ((j = 0; j < 8; j += 2))
369         do
370                 ((i = j + 1))
371                 ((y = ${boxCur[$j]} + boxCurY))
372                 ((x = ${boxCur[$i]} + boxCurX))
373                 for ((k = y + 1; k < iTrayHeight; k++))
374                 do
375                         ((i = k * iTrayWidth + x))
376                         if (( ${iMap[$i]} != -1)); then break; fi
377                 done
378                 ((k -= y + 1))
379                 if (( $iDown > $k )); then iDown=$k; fi
380         done
381  
382         s=`DrawCurBox 0`        #将旧的方块抹去
383         ((boxCurY += iDown))
384         s=$s`DrawCurBox 1`        #显示新的下落后的方块
385         echo -ne $s
386         Box2Map                #将当前移动中的方块贴到背景方块中
387         RandomBox        #产生新的方块
388 }
389  
390  
391 #旋转方块
392 function BoxRotate()
393 {
394         local iCount iTestRotate boxTest j i s
395         iCount=${countBox[$iBoxCurType]}        #当前的方块经旋转可以产生的样式的数目
396  
397         #计算旋转后的新的样式
398         ((iTestRotate = iBoxCurRotate + 1))
399         if ((iTestRotate >= iCount))
400         then
401                 ((iTestRotate = 0))
402         fi
403  
404         #更新到新的样式, 保存老的样式(但不显示)
405         for ((j = 0, i = (${offsetBox[$iBoxCurType]} + $iTestRotate) * 8; j < 8; j++, i++))
406         do
407                 boxTest[$j]=${boxCur[$j]}
408                 boxCur[$j]=${box[$i]}
409         done
410  
411         if BoxMove $boxCurY $boxCurX        #测试旋转后是否有空间放的下
412         then
413                 #抹去旧的方块
414                 for ((j = 0; j < 8; j++))
415                 do
416                         boxCur[$j]=${boxTest[$j]}
417                 done
418                 s=`DrawCurBox 0`
419  
420                 #画上新的方块
421                 for ((j = 0, i = (${offsetBox[$iBoxCurType]} + $iTestRotate) * 8; j < 8; j++, i++))
422                 do
423                         boxCur[$j]=${box[$i]}
424                 done
425                 s=$s`DrawCurBox 1`
426                 echo -ne $s
427                 iBoxCurRotate=$iTestRotate
428         else
429                 #不能旋转,还是继续使用老的样式
430                 for ((j = 0; j < 8; j++))
431                 do
432                         boxCur[$j]=${boxTest[$j]}
433                 done
434         fi
435 }
436  
437  
438 #DrawCurBox(bDraw), 绘制当前移动中的方块, bDraw为1, 画上, bDraw为0, 抹去方块。
439 function DrawCurBox()
440 {
441         local i j t bDraw sBox s
442         bDraw=$1
443  
444         s=""
445         if (( bDraw == 0 ))
446         then
447                 sBox="\040\040"
448         else
449                 sBox="[]"
450                 s=$s"\033[1m\033[7m\033[3${cBoxCur}m\033[4${cBoxCur}m"
451         fi
452  
453         for ((j = 0; j < 8; j += 2))
454         do
455                 ((i = iTrayTop + 1 + ${boxCur[$j]} + boxCurY))
456                 ((t = iTrayLeft + 1 + 2 * (boxCurX + ${boxCur[$j + 1]})))
457                 #\033[y;xH, 光标到(x, y)处
458                 s=$s"\033[${i};${t}H${sBox}"
459         done
460         s=$s"\033[0m"
461         echo -n $s
462 }
463  
464  
465 #更新新的方块
466 function RandomBox()
467 {
468         local i j t
469  
470         #更新当前移动的方块
471         iBoxCurType=${iBoxNewType}
472         iBoxCurRotate=${iBoxNewRotate}
473         cBoxCur=${cBoxNew}
474         for ((j = 0; j < ${#boxNew[@]}; j++))
475         do
476                 boxCur[$j]=${boxNew[$j]}
477         done
478  
479  
480         #显示当前移动的方块
481         if (( ${#boxCur[@]} == 8 ))
482         then
483                 #计算当前方块该从顶端哪一行""出来
484                 for ((j = 0, t = 4; j < 8; j += 2))
485                 do
486                         if ((${boxCur[$j]} < t)); then t=${boxCur[$j]}; fi
487                 done
488                 ((boxCurY = -t))
489                 for ((j = 1, i = -4, t = 20; j < 8; j += 2))
490                 do
491                         if ((${boxCur[$j]} > i)); then i=${boxCur[$j]}; fi
492                         if ((${boxCur[$j]} < t)); then t=${boxCur[$j]}; fi
493                 done
494                 ((boxCurX = (iTrayWidth - 1 - i - t) / 2))
495  
496                 #显示当前移动的方块
497                 echo -ne `DrawCurBox 1`
498  
499                 #如果方块一出来就没处放,Game over!
500                 if ! BoxMove $boxCurY $boxCurX
501                 then
502                         kill -$sigExit ${PPID}
503                         ShowExit
504                 fi
505         fi
506  
507  
508  
509         #清除右边预显示的方块
510         for ((j = 0; j < 4; j++))
511         do
512                 ((i = iTop + 1 + j))
513                 ((t = iLeft + 2 * iTrayWidth + 7))
514                 echo -ne "\033[${i};${t}H        "
515         done
516  
517         #随机产生新的方块
518         ((iBoxNewType = RANDOM % ${#offsetBox[@]}))
519         ((iBoxNewRotate = RANDOM % ${countBox[$iBoxNewType]}))
520         for ((j = 0, i = (${offsetBox[$iBoxNewType]} + $iBoxNewRotate) * 8; j < 8; j++, i++))
521         do
522                 boxNew[$j]=${box[$i]};
523         done
524  
525         ((cBoxNew = ${colorTable[RANDOM % ${#colorTable[@]}]}))
526  
527         #显示右边预显示的方块
528         echo -ne "\033[1m\033[7m\033[3${cBoxNew}m\033[4${cBoxNew}m"
529         for ((j = 0; j < 8; j += 2))
530         do
531                 ((i = iTop + 1 + ${boxNew[$j]}))
532                 ((t = iLeft + 2 * iTrayWidth + 7 + 2 * ${boxNew[$j + 1]}))
533                 echo -ne "\033[${i};${t}H[]"
534         done
535         echo -ne "\033[0m"
536 }
537  
538  
539 #初始绘制
540 function InitDraw()
541 {
542         clear
543         RandomBox        #随机产生方块,这时右边预显示窗口中有方快了
544         RandomBox        #再随机产生方块,右边预显示窗口中的方块被更新,原先的方块将开始下落
545         local i t1 t2 t3
546  
547         #显示边框
548         echo -ne "\033[1m"
549         echo -ne "\033[3${cBorder}m\033[4${cBorder}m"
550  
551         ((t2 = iLeft + 1))
552         ((t3 = iLeft + iTrayWidth * 2 + 3))
553         for ((i = 0; i < iTrayHeight; i++))
554         do
555                 ((t1 = i + iTop + 2))
556                 echo -ne "\033[${t1};${t2}H||"
557                 echo -ne "\033[${t1};${t3}H||"
558         done
559  
560         ((t2 = iTop + iTrayHeight + 2))
561         for ((i = 0; i < iTrayWidth + 2; i++))
562         do
563                 ((t1 = i * 2 + iLeft + 1))
564                 echo -ne "\033[${iTrayTop};${t1}H=="
565                 echo -ne "\033[${t2};${t1}H=="
566         done
567         echo -ne "\033[0m"
568  
569  
570         #显示"Score""Level"字样
571         echo -ne "\033[1m"
572         ((t1 = iLeft + iTrayWidth * 2 + 7))
573         ((t2 = iTop + 10))
574         echo -ne "\033[3${cScore}m\033[${t2};${t1}HScore"
575         ((t2 = iTop + 11))
576         echo -ne "\033[3${cScoreValue}m\033[${t2};${t1}H${iScore}"
577         ((t2 = iTop + 13))
578         echo -ne "\033[3${cScore}m\033[${t2};${t1}HLevel"
579         ((t2 = iTop + 14))
580         echo -ne "\033[3${cScoreValue}m\033[${t2};${t1}H${iLevel}"
581         echo -ne "\033[0m"
582 }
583  
584  
585 #退出时显示GameOVer!
586 function ShowExit()
587 {
588         local y
589         ((y = iTrayHeight + iTrayTop + 3))
590         echo -e "\033[${y};0HGameOver!\033[0m"
591         exit
592 }
593  
594  
595 #显示用法.
596 function Usage
597 {
598         cat << EOF
599 Usage: $APP_NAME
600 Start tetris game.
601  
602   -h, --help              display this help and exit
603       --version           output version information and exit
604 EOF
605 }
606  
607  
608 #游戏主程序在这儿开始.
609 if [[ "$1" == "-h" || "$1" == "--help" ]]; then
610         Usage
611 elif [[ "$1" == "--version" ]]; then
612         echo "$APP_NAME $APP_VERSION"
613 elif [[ "$1" == "--show" ]]; then
614         #当发现具有参数--show时,运行显示函数
615         RunAsDisplayer
616 else
617         bash $0 --show&        #以参数--show将本程序再运行一遍
618         RunAsKeyReceiver $!        #以上一行产生的进程的进程号作为参数
619 fi

 

以上是关于用EXCEL编写俄罗斯方块小游戏(基于VBA)的主要内容,如果未能解决你的问题,请参考以下文章

软件设计实战:基于Java的俄罗斯方块游戏完整版

用Shell编写的俄罗斯方块代码

求用JAVA编写俄罗斯方块游戏的源代码

用c语言编写俄罗斯方块程序 求详解

基于python的俄罗斯方块小游戏

基于.NET的俄罗斯方块课程设计