用EXCEL编写俄罗斯方块小游戏(基于VBA)
Posted wrwttsy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用EXCEL编写俄罗斯方块小游戏(基于VBA)相关的知识,希望对你有一定的参考价值。
用EXCEL编写俄罗斯方块小游戏(基于VBA)
工作属性原因,工作中使用excel办公是常态。前一阵子因工作业务需求,需要用到VBA。研究了一阵子VBA,解决了当时的需求。
后来想想,VBA可以如此彻底的控制excel,那么可不可以编个小游戏呢。
说干就干,先拿与表格最像的俄罗斯方块试试手。
预览成品效果 (文末附下载地址┗( ▔, ▔ )┛)
第一步:准备工作
首先,俄罗斯方块游戏需要完成哪些工作。
- 设置游戏窗口大小:俄罗斯方块游戏窗口大小为横10个方格、竖20个方格。
- 设置可变形方块元素:俄罗斯方块一共7种不同样式的方块。
- 设置游戏交互:俄罗斯方块有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]。以免出现下图现象,影响体验。
(四)保持游戏正常运行
- 碰撞检测实现。
设计思路:
用一维数组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
- 是否可以移动或变形检测。
移动检测:检测方块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
- 得分判定及消除。
设计思路:每次方块掉落结束与下次方块掉落间隙。对游戏窗口进行一次自上而下,自左至右的遍历,判断某行是否已经塞满方块。
若某行塞满,则分数+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
- 其他
①全局变量及初始化配置
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)的主要内容,如果未能解决你的问题,请参考以下文章