用8086汇编语言写新春祝福

本篇目录

一、前言

1.创作背景

2.最终效果

3.必要的准备

二、实现步骤

1.程序框架

2.使程序暂停一段时间的子程序

3.显示一朵烟花的子程序

(1)参数

(2)地址转换

(3)显示花柄

(4)清除花柄

(5)显示花瓣

3.显示一段字符的子程序

(1)参数

(2)显示字符

三、完整代码


一、前言

1.创作背景

马上就要到龙年春节了,作为热爱编程的人,我决定动手写个8086汇编语言的小程序作为新春祝福,顺道也复习一下汇编语言。本文一步步展示了程序的实现过程,最终效果如下,完整代码放在最后。

祝大家龙年大吉,健康快乐!        2024.2.1

2.最终效果

(烟花及标语都是动态的,但是在CSDN传视频比较麻烦,担心视频失效,这里就先放张图片。)

新春祝福-8086汇编语言

3.必要的准备

硬件和软件

我的电脑是64位win10系统,软件用的是VSCode(使用教程见 《使用VSCode编写汇编程序》 )。

也可以用记事本写代码,然后在DosBox中运行,也一样。只要能运行汇编程序应该都可以。

编程基础

主要涉及8086汇编语言的字符显示、子程序设计、循环语句等内容。目前汇编方面我学过《汇编语言》(王爽)教材,足够完成这个小项目了。


二、实现步骤

1.程序框架

这个程序的主要功能,是在屏幕指定位置动态地显示绽放的烟花以及祝福标语。烟花不只一朵,因此就写一个子程序show_fireworks来实现“显示一朵烟花”的功能,然后在主程序中多次调用,只要设置不同的参数即可。祝福标语的显示也写一个子程序show_area来实现。这个显示标语的功能,其实就是在指定的屏幕起始位置,按照指定的行数、列数来显示指定的字符串,这是比较通用的功能,所以单独写一个子程序来方便以后使用。最后,要想实现动态显示的效果,就要让程序在显示完一个字符后停顿一定的时间,因此还要写一个show_pause子程序来实现这个功能。

由此得程序的整体框架如下。

assume cs:codd,ss:stack,ds:data

data segment    ;数据段
    dw 0       
data ends      

stack segment    ;栈段 

code segment    ;代码段

    start:      ;程序入口

    call show_fireworks   ;在屏幕指定位置显示一朵烟花

    call show_area    ;在屏幕指定位置显示祝福标语


mov ax,4c00H    ;程序返回
int 21H

show_fireworks:   ;在屏幕指定位置显示一朵烟花

show_area:    ;在屏幕指定位置显示祝福标语

show_pause:    ;让程序暂停一会儿

code ends   
end start   

2.使程序暂停一段时间的子程序

 由简入繁,先来写这个最简单的子程序。需要的参数只有一个,就是想要暂停的时间,用cx来传递。这里的程序暂停,是依靠循环执行nop指令来实现的。我是用内外双循环来实现这个功能的,这样相当于可以实现O(n^2)的暂停时间(暂停时间与参数的平方成正比)。只用单层循环来实现也没问题。代码如下。

;功能:产生一段时间的暂停(时间与参数的平方成正比)
;参数:cx 暂停的时间
;返回:无
show_pause:     ;子程序show_pause开始
    push cx     ;将用到的寄存器压入栈
    push ax
    mov ax,cx   ;将接收的时间参数传送给ax,以ax为内层循环的次数
    show_pause_s:
        push cx     ;将外层循环的次数压入栈
        mov cx,ax   ;设置内层循环的参数
        show_pause_s0:
            nop         ;空指令,产生一段时间的暂停
            loop show_pause_s0  ;内层循环
        pop cx      ;将外层循环的次数pop出栈
        loop show_pause_s   ;外层循环
    pop ax      ;将用到的寄存器pop出栈
    pop cx
    ret         ;show_pause子程序返回

3.显示一朵烟花的子程序

(1)参数

在这个程序中,所有烟花的结构都是相同的,由花柄和8个花瓣组成。构成烟花的字符、字符属性、花柄的长度、花瓣的长度、烟花绽放的速度、指定的显示位置等都是子程序的参数。由于参数比较多,用寄存器来传递参数不太现实,因此就在数据段中存储参数,然后把参数列表的初始地址ds:si 作为参数传递给子程序就可以了。在数据段的参数列表中存储的参数,相当于是这些参数的初始值。

一些烟花通用的参数,比如字符属性,用data段中的标号show_fireworks来标识。另外,烟花的花瓣是要从花心出发向周围八个方向伸展,因此在数据段中要存储字符的偏移地址相对于花心的位移量,这部分数据用标号show_fireworks_addr来标识。当然也可以改变花瓣的形状,我这里只是画出了比较经典的八瓣花形状,相对花心位置的偏移量我是用Excel计算的。

因为要显示多朵烟花,所以,在主程序中进行调用的时候,可以设置不同的参数,这样就可以让烟花具有不同的颜色、大小、绽放速度、构成字符等属性,视觉效果相对丰富一些。

以下是data段(数据段)中show_fireworks子程序的参数列表。

data segment

    ;show_fireworks子程序的参数列表
        ;地址       参数含义
        ; 00        屏幕起始显示位置的行号
        ; 01        屏幕起始显示位置的列号
        ; 02        烟花花柄长度(>1)
        ; 03        字符的属性字节
        ; 04        用于构成烟花的单个字符
        ; 05        烟花花瓣的长度
        ; 06        显示花径每个字符(或花瓣每一组8个字符)后停顿的时间 低字节
        ; 07        显示花径每个字符(或花瓣每一组8个字符)后停顿的时间 高字节
        ; 08        清除花径每个字符后停顿的时间 低字节
        ; 09        清除花径每个字符后停顿的时间 高字节
        ; 0A-2AH    以字单元依次指明要显示字符的相对地址,8个为一组
    args_show_fireworks db 15H,12H,0BH,04H,'*',5H,0B0H,0H ;00-07字节
    db 0C0H,00H ;08-09字节
    args_show_fireworks_addr dw 0FF60H
    dw 0FF62H,4H,0A2H,0A0H,9EH,0FFFCH,0FF5EH     ;第一层(最靠近花心)
    dw 0FEC0H,0FEC4H,8H,144H,140H,13CH,0FFF8H,0FEBCH    ;第二层
    dw 0FE20H,0FE26H,0CH,1E6H,1E0H,1DAH,0FFF4H,0FE1AH   ;第三层
    dw 0FD80H,0FD88H,10H,288H,280H,278H,0FFF0H,0FD78H   ;第四层
    dw 0FCE0H,0FCEAH,14H,32AH,320H,316H,0FFECH,0FCD6H   ;第五层

data ends

(2)地址转换

花柄要从指定的行号和列号处开始显示,因此这里首先要做的一步工作就是,将指定的行号和列号转换为显示缓冲区的偏移地址。这个功能很常用,接下来的show_area(显示祝福标语)子程序中也会用到。但是这步转换一共只需要八行代码,为此写一个子程序实在不值当,毕竟汇编语言的子程序还要写一堆push 和pop。代码如下。

    ;设置es段指向显示缓冲区段
    mov ax,0B800H   
    mov es,ax

    ;设置es:di指向显示缓冲区指定显示位置的首地址
    mov al,0A0H                 ;每行80个字符,每个字符占2字节,所以每行共80*2=160=A0H个字节
    mul byte ptr ds:[si+0]      ;计算指定行的起始地址(al * 行号 → ax)
    mov di,ax                   ;将乘法结果存放在di中(di = al * 行号)
    mov al,02H                  ;每个字符占2个字节
    mul byte ptr ds:[si+1]      ;计算目标行在指定位置前的字节数(al * 列号 → ax)
    add di,ax                   ;计算指定显示位置在es段的偏移地址(di + ax → di)

(3)显示花柄

在这个程序中,花柄是竖直向上、逐个字符显示的。长度、颜色等由参数指定,参数要从data段的参数列表中获取。每显示一个字符后,就调用show_pause子程序来让程序停顿一会儿,这样就有了动态效果。

这里还有一个小细节。在完成行号、列号→偏移地址的转换后,es:di指向屏幕指定的起始显示地址。之后,在显示花柄的过程中会改变di的值。但是接下来清除花柄的过程,还要从起始位置开始进行,因此这个起始位置是要保留的,程序中就用bx=di来暂存。

显示花柄的代码如下。

    ;显示烟花的花柄
    mov al,ds:[si+4]      ;获取参数:构成烟花的单个字符
    mov ah,ds:[si+3]    ;获取参数:字符的属性字节
    mov bx,di           ;使用bx暂存di,es:di指向显示起始位置
    mov ch,0        ;获取参数:花柄长度
    mov cl,ds:[si+2]    ;以花柄长度作为循环次数
    show_fireworks_s1:  ;显示花柄
        mov es:[di],al   ;将字符传送到显存区
        mov es:[di+1],ah    ;将字符的属性字节传送到显存
        push cx
        mov cx,ds:[si+6]    ;设置程序暂停子程序的时间参数
        call show_pause ;调用子程序,使程序暂停一会儿
        pop cx
        sub di,0A0H         ;ds:di指向行号-1的地址
        loop show_fireworks_s1  ;循环结束,花柄显示完毕

(4)清除花柄

花柄在显示完成之后,还要从下到上逐渐消失,这是模拟真实烟花的效果。这其实是从下到上显示了一个空格字符。在花柄显示过程中,所用字符是从数据段的参数列表中获取的,而在花柄消失(清除花柄)的过程中,这个空格字符是在程序中定义的。

另外,烟花个人认为还是保留花心比较好看,因此在这个花柄消失过程中,就把花柄长度-1作为循环次数,这样就能保留花心。其余部分和上面显示花柄的代码基本相同。

在清除花柄的过程结束之后,es:di再次指向花心,这为接下来显示花瓣提供了便利。代码如下。

    ;清除烟花的花柄
    mov al,' '           ;要显示的字符是一个空格
    mov ah,0             ;字符的属性字节
    mov ch,0        ;获取参数:花柄长度
    mov cl,ds:[si+2]    ;以花柄长度-1作为循环次数,保留花心
    dec cl
    mov di,bx           ;从bx中恢复di,es:di指向显示起始位置
    show_fireworks_s2:   ;清除花柄
        mov es:[di],al   ;将空格字符传送到显存区
        mov es:[di+1],ah    ;将字符的属性字节传送到显存
        push cx
        mov cx,ds:[si+8]    ;设置程序暂停子程序的时间参数
        call show_pause ;调用子程序,使程序暂停一会儿
        pop cx
        sub di,0A0H         ;es:di指向行号-1的地址
        loop show_fireworks_s2  ;循环结束,花柄清除完毕
        ;至此es:di指向花心

(5)显示花瓣

接下来是花瓣,这里用的全部是八瓣花,所以“8”这个数值是硬编码在程序中的。显示花瓣的策略有很多,我这里用的是内外双循环,外循环负责每次将花瓣(八个方向的)长度+1,内循环的循环次数为8,负责在8个方向各显示一个字符。内循环执行过程中不设置停顿,因此视觉效果就是,八个方向的花瓣是同时长度+1的。而外循环中包含一次对show_pause子程序的调用,视觉效果就是,烟花是从花心向外一层层逐渐绽放的(而不是一下子全都显示出来)。代码如下。

    ;显示烟花的花瓣(八瓣花)
    mov ah,ds:[si+3]    ;获取参数:字符的属性字节
    mov al,ds:[si+4]    ;获取参数:构成烟花的单个字符
    mov cl,ds:[si+5]    ;获取参数:烟花花瓣的长度
    mov si,offset args_show_fireworks_addr  ;获取参数:相对地址列表首地址
    mov ch,0            ;设置外层循环次数为花瓣的长度
    show_fireworks_s:
        push cx         ;将外层循环次数压入栈
        mov cx,08H       ;设置内层循环的次数为8,硬编码,因为是八瓣花
        show_fireworks_s0:  ;内层循环,显示8个方向的字符
            mov bx,ds:[si]  ;获取参数:显存区字符的相对地址
            mov es:[di+bx],al   ;将字符传送到显存区
            mov es:[di+bx+1],ah ;将字符的属性字节传送到显存区
            add si,2            ;ds:si指向下一个字符显示的相对地址
            loop show_fireworks_s0  ;显示完8个方向的各一个字符
        push cx
        mov cx,0200H    ;设置时间参数
        call show_pause ;调用子层序,使程序暂停一会儿
        pop cx          
        pop cx          ;将外层循环的循环次数pop出栈
        loop show_fireworks_s   ;八瓣花显示完毕

3.显示一段字符的子程序

(1)参数

这个子程序的功能是,在指定的起始位置,按照指定的行数、列数(可以理解为要显示一个矩形区域的字符)显示一段字符,那么必然要指定起始起始位置的行号和列号、指定显示的行数和列数、要显示的字符及字符属性等。由此可知,这个子程序要用到的参数也比较多,那么同样我们就在data段中开辟一段空间来存放参数列表,然后把参数列表的首地址作为参数传递给这个子程序就可以了。

这个祝福标语,从视觉效果来看,显示的字符只有一行。但是,我用了橙底青字来突出显示,并且在祝福语周围加了一圈橙色边框(这是模拟在红纸上写祝福语,毕竟过春节嘛)。所以其实程序中要显示的字符行数为三行,只不过第一行和第三行显示的字符全都是空格。要显示的祝福语字符串,在data段(数据段)中用标号data_str来标识。

除了祝福标语外,我还在屏幕右下角显示了一行字符。个人认为,如果不写这行字符的话,整个画面会有点左右失衡,加上这么一行字符,视觉效果更好。字符的内容可以任意修改,不过要注意,在子程序中调用的时候,要设置好相应的行数、列数参数。要在右下角显示的字符串,在数据段中使用标号data_author来标识。

以下是数据段中show_area子程序的参数列表。

data segment

    ;show_area子程序的参数列表
        ;地址   参数含义
        ; 00    屏幕起始显示位置的行号
        ; 01    屏幕起始显示位置的列号
        ; 02    待显示字符的行数
        ; 03    待显示字符的列数
        ; 04    待显示字符的属性字节
        ; 05    显示一行后停顿的时间 低字节
        ; 06    显示一行后停顿的时间 高字节
        ; 07-0FH 备用
    args_show_area db 10H dup (0) 

    ;show_area子程序  待显示的祝福标语字符串
    data_str db 26 dup (' ')
    db '  Happy Spring Festival!  '
    db 26 dup (' ')

    ;show_area子程序  待显示的作者信息
    data_author db 'Dr.Cheese 2024  '

data ends

(2)显示字符

在开始显示字符之前,首先同样要将给定的起始行号、列号转换为显示缓冲区段的偏移地址,这一步在show_fireworks子程序中已经写过了,这里不再赘述。

接下来就要按照指定的行数、列数来显示字符了。我使用内外双循环来实现,外层循环每次负责显示一行字符,内层循环每次负责显示当前行中的一个字符。这里要注意的就是,循环次数使用cx寄存器来存放,因此设置内层循环的循环次数之前要先将外层循环的循环次数push压入栈,等内循环结束,再pop出来。

另外,我还在这个子程序中增加了一点动态效果,就是每显示完一行字符就调用show_pause子程序来使程序停顿一会儿,这样做的视觉效果就是,字符串(或者说矩形的字符区域)是由上到下逐渐展开的。(有点类似于PPt中的“展开”动画效果。)

这一部分的实现代码如下。

    ;通过循环来显示字符串
    mov ch,0            ;设置外层循环次数为待显示字符的行数
    mov cl,ds:[bx+2]    
    show_area_s:        ;外层循环开始
        push cx             ;将外层循环次数压入栈
        mov ch,0            ;设置内存循环次数为待显示字符的列数
        mov cl,ds:[bx+3]    
        show_area_s1:       ;内层循环开始
            mov al,ds:[si]      ;将ds:si指向的字节传送给es:di
            mov es:[di],al        
            inc di              ;es:di指向显存区下一个字节(当前字符的属性字节)
            mov al,ds:[bx+4]    ;将字符的属性字节传送给es:di
            mov es:[di],al
            inc si              ;ds:si指向下一个data段中要显示的字符
            inc di              ;es:di指向显存区下一个字节(下一个字符的ASCII码字节)
            loop show_area_s1   ;内层循环
        mov cx,ds:[bx+5]        ;设置程序暂停循环的次数
        call show_pause         ;调用子程序产生一段时间的暂停
        add di,0A0H             ;设置es:di指向显存区下一行
        mov al,ds:[bx+3] 
        mov ah,0
        sub di,ax 
        sub di,ax
        pop cx                  ;将外层循环的次数pop出栈
        loop show_area_s        ;外层循环


三、完整代码

最后是这个程序的完整代码,在本机测试通过。

;程序功能:在屏幕上显示烟花绽放动画及新春祝福标语
assume cs:code, ds:data ,ss:stack
stack segment       ;定义栈段
    dw 32 dup (0)
stack ends

data segment        ;定义数据段

    ;show_fireworks子程序的参数列表
        ;地址       参数含义
        ; 00        屏幕起始显示位置的行号
        ; 01        屏幕起始显示位置的列号
        ; 02        烟花花柄长度(>1)
        ; 03        字符的属性字节
        ; 04        用于构成烟花的单个字符
        ; 05        烟花花瓣的长度
        ; 06        显示花径每个字符(或花瓣每一组8个字符)后停顿的时间 低字节
        ; 07        显示花径每个字符(或花瓣每一组8个字符)后停顿的时间 高字节
        ; 08        清除花径每个字符后停顿的时间 低字节
        ; 09        清除花径每个字符后停顿的时间 高字节
        ; 0A-2AH    以字单元依次指明要显示字符的相对地址,8个为一组
    args_show_fireworks db 15H,12H,0BH,04H,'*',5H,0B0H,0H ;00-07字节
    db 0C0H,00H ;08-09字节
    args_show_fireworks_addr dw 0FF60H
    dw 0FF62H,4H,0A2H,0A0H,9EH,0FFFCH,0FF5EH     ;第一层(最靠近花心)
    dw 0FEC0H,0FEC4H,8H,144H,140H,13CH,0FFF8H,0FEBCH    ;第二层
    dw 0FE20H,0FE26H,0CH,1E6H,1E0H,1DAH,0FFF4H,0FE1AH   ;第三层
    dw 0FD80H,0FD88H,10H,288H,280H,278H,0FFF0H,0FD78H   ;第四层
    dw 0FCE0H,0FCEAH,14H,32AH,320H,316H,0FFECH,0FCD6H   ;第五层

    ;show_area子程序的参数列表
        ;地址   参数含义
        ; 00    屏幕起始显示位置的行号
        ; 01    屏幕起始显示位置的列号
        ; 02    待显示字符的行数
        ; 03    待显示字符的列数
        ; 04    待显示字符的属性字节
        ; 05    显示一行后停顿的时间 低字节
        ; 06    显示一行后停顿的时间 高字节
        ; 07-0FH 备用
    args_show_area db 10H dup (0) 

    ;show_area子程序  待显示的祝福标语字符串
    data_str db 26 dup (' ')
    db '  Happy Spring Festival!  '
    db 26 dup (' ')

    ;show_area子程序  待显示的作者信息
    data_author db 'Dr.Cheese 2024  '

data ends

code segment      ;定义代码段
start:              ;程序入口

    ;设置段地址
    mov ax,stack    ;设置SS:SP指向栈顶
    mov ss,ax
    mov sp,40H
    mov ax,data     ;设置ds指向data段
    mov ds,ax   

    ;设置参数,并调用show_fireworks子程序,显示第一朵烟花
    mov si,offset args_show_fireworks   ;ds:bx指向show_fireworks子程序的参数列表首地址
    mov byte ptr ds:[si+0],16H      ;设置参数:烟花显示起始位置行号
    mov byte ptr ds:[si+1],11H      ;设置参数:烟花显示起始位置列号
    mov byte ptr ds:[si+2],0BH      ;设置参数:烟花花柄长度
    mov byte ptr ds:[si+3],03H      ;设置参数:字符属性字节,青色
    mov byte ptr ds:[si+4],'z'      ;设置参数:构成烟花的单个字符
    mov byte ptr ds:[si+5],05H      ;设置参数:花瓣的长度
    add word ptr ds:[si+6],0001H    ;设置参数:显示花径每个字符(或花瓣每一组8个字符)后停顿的时间
    add word ptr ds:[si+8],0001H    ;设置参数:清除花径每个字符后停顿的时间
    call show_fireworks     ;调用子程序显示一朵烟花

    ;设置参数,并调用show_fireworks子程序,显示第二朵烟花
    mov si,offset args_show_fireworks   ;ds:bx指向show_fireworks子程序的参数列表首地址
    mov byte ptr ds:[si+0],12H      ;设置参数:烟花显示起始位置行号
    mov byte ptr ds:[si+1],3EH      ;设置参数:烟花显示起始位置列号
    mov byte ptr ds:[si+2],0BH      ;设置参数:烟花花柄长度
    mov byte ptr ds:[si+3],0CH      ;设置参数:字符属性字节
    mov byte ptr ds:[si+4],'o'      ;设置参数:构成烟花的单个字符
    mov byte ptr ds:[si+5],05H      ;设置参数:花瓣的长度
    add word ptr ds:[si+6],0004H    ;设置参数:显示花径每个字符(或花瓣每一组8个字符)后停顿的时间
    add word ptr ds:[si+8],0004H    ;设置参数:清除花径每个字符后停顿的时间
    call show_fireworks     ;调用子程序显示一朵烟花

    ;设置参数,并调用show_fireworks子程序,显示第三朵烟花
    mov si,offset args_show_fireworks   ;ds:bx指向show_fireworks子程序的参数列表首地址
    mov byte ptr ds:[si+0],11H      ;设置参数:烟花显示起始位置行号
    mov byte ptr ds:[si+1],22H      ;设置参数:烟花显示起始位置列号
    mov byte ptr ds:[si+2],0BH      ;设置参数:烟花花柄长度
    mov byte ptr ds:[si+3],0DH      ;设置参数:字符属性字节
    mov byte ptr ds:[si+4],'$'      ;设置参数:构成烟花的单个字符
    mov byte ptr ds:[si+5],05H      ;设置参数:花瓣的长度
    add word ptr ds:[si+6],-006H    ;设置参数:显示花径每个字符(或花瓣每一组8个字符)后停顿的时间
    add word ptr ds:[si+8],-006H    ;设置参数:清除花径每个字符后停顿的时间
    call show_fireworks     ;调用子程序显示一朵烟花

    ;设置参数,并调用show_fireworks子程序,显示第四朵烟花
    mov si,offset args_show_fireworks   ;ds:bx指向show_fireworks子程序的参数列表首地址
    mov byte ptr ds:[si+0],17H      ;设置参数:烟花显示起始位置行号
    mov byte ptr ds:[si+1],2FH      ;设置参数:烟花显示起始位置列号
    mov byte ptr ds:[si+2],07H      ;设置参数:烟花花柄长度
    mov byte ptr ds:[si+3],0EH      ;设置参数:字符属性字节
    mov byte ptr ds:[si+4],'x'      ;设置参数:构成烟花的单个字符
    mov byte ptr ds:[si+5],05H      ;设置参数:花瓣的长度
    add word ptr ds:[si+6],0001H    ;设置参数:显示花径每个字符(或花瓣每一组8个字符)后停顿的时间
    add word ptr ds:[si+8],0001H    ;设置参数:清除花径每个字符后停顿的时间
    call show_fireworks     ;调用子程序显示一朵烟花

    ;程序暂停一会儿
    mov cx,01C0H        ;参数:暂停的时间
    call show_pause     ;调用子程序,使程序暂停一会儿

    ;设置参数,并调用show_area子程序,显示祝福标语
    mov si,offset data_str       ;参数:ds:si指向待显示字符的首地址
    mov bx,offset args_show_area    ;参数:ds:bx指向show_area子程序的参数列表首地址
    mov byte ptr ds:[bx+0],09H   ;参数:屏幕显示起始行号
    mov byte ptr ds:[bx+1],1AH   ;参数:屏幕显示起始列号
    mov byte ptr ds:[bx+2],03H   ;参数:待显示字符的行数
    mov byte ptr ds:[bx+3],1AH   ;参数:待显示字符的列数
    mov byte ptr ds:[bx+4],4BH   ;参数:待显示字符的属性    橙底青色
    mov word ptr ds:[bx+5],0170H   ;参数:显示一行后暂停的时间
    call show_area       ;调用show_area子程序

    ;程序暂停一会儿
    mov cx,01C0H        ;参数:暂停的时间
    call show_pause     ;调用子程序,使程序暂停一会儿

    ;设置参数,并调用show_fireworks子程序,显示第五朵烟花
    mov si,offset args_show_fireworks   ;ds:bx指向show_fireworks子程序的参数列表首地址
    mov byte ptr ds:[si+0],18H      ;设置参数:烟花显示起始位置行号
    mov byte ptr ds:[si+1],1FH      ;设置参数:烟花显示起始位置列号
    mov byte ptr ds:[si+2],07H      ;设置参数:烟花花柄长度
    mov byte ptr ds:[si+3],0AH      ;设置参数:字符属性字节
    mov byte ptr ds:[si+4],'#'      ;设置参数:构成烟花的单个字符
    mov byte ptr ds:[si+5],03H      ;设置参数:花瓣的长度
    add word ptr ds:[si+6],0001H    ;设置参数:显示花径每个字符(或花瓣每一组8个字符)后停顿的时间
    add word ptr ds:[si+8],0001H    ;设置参数:清除花径每个字符后停顿的时间
    call show_fireworks     ;调用子程序显示一朵烟花

    ;设置参数,并调用show_area子程序,显示创作者信息
    mov si,offset data_author       ;参数:ds:si指向待显示字符的首地址
    mov bx,offset args_show_area    ;参数:ds:bx指向show_area子程序的参数列表首地址
    mov byte ptr ds:[bx+0],15H   ;参数:屏幕显示起始行号
    mov byte ptr ds:[bx+1],3AH   ;参数:屏幕显示起始列号
    mov byte ptr ds:[bx+2],01H   ;参数:待显示字符的行数
    mov byte ptr ds:[bx+3],10H   ;参数:待显示字符的列数
    mov byte ptr ds:[bx+4],03H   ;参数:待显示字符的属性    青色
    mov word ptr ds:[bx+5],0100H   ;参数:显示一行后暂停的时间
    call show_area       ;调用show_area子程序

    ;程序暂停一会儿
    mov cx,01C0H        ;参数:暂停的时间
    call show_pause     ;调用子程序,使程序暂停一会儿

    mov ax,4c00H    ;程序返回
    int 21H

;功能:在屏幕指定起始位置,以指定颜色,以指定行数和列数显示指定的字符串
;参数:   
    ;ds:si 指向data段字符串的首地址
    ;ds:bx 指向参数列表的首地址
        ;show_area子程序的参数列表如下:
        ;地址   参数含义
        ; 00    屏幕起始显示位置的行号
        ; 01    屏幕起始显示位置的列号
        ; 02    待显示字符的行数
        ; 03    待显示字符的列数
        ; 04    待显示字符的属性字节
        ; 05    显示一行后停顿的时间 低字节
        ; 06    显示一行后停顿的时间 高字节
;返回:无
show_area:               ;show_area子程序开始
    ;将子程序用到的寄存器压入栈
    push ax         
    push es
    push di
    push cx

    mov ax,0B800H    ;设置es指向显示缓冲区段
    mov es,ax

    ;设置es:di指向显示缓冲区指定显示位置的首地址
    mov al,0A0H                 ;每行80个字符,每个字符占2字节,所以每行共80*2=160=A0H个字节
    mul byte ptr ds:[bx+0]      ;计算第dh行起始地址(al * 行号 → ax)
    mov di,ax                   ;将乘法结果存放在di中(di = al * 行号)
    mov al,02H                  ;每个字符占2个字节
    mul byte ptr ds:[bx+1]      ;计算目标行在指定位置前的字节数(al * 列号 → ax)
    add di,ax                   ;计算指定显示位置在es段的偏移地址(di + ax → di)

    ;通过循环来显示字符串
    mov ch,0            ;设置外层循环次数为待显示字符的行数
    mov cl,ds:[bx+2]    
    show_area_s:        ;外层循环开始
        push cx             ;将外层循环次数压入栈
        mov ch,0            ;设置内存循环次数为待显示字符的列数
        mov cl,ds:[bx+3]    
        show_area_s1:       ;内层循环开始
            mov al,ds:[si]      ;将ds:si指向的字节传送给es:di
            mov es:[di],al        
            inc di              ;es:di指向显存区下一个字节(当前字符的属性字节)
            mov al,ds:[bx+4]    ;将字符的属性字节传送给es:di
            mov es:[di],al
            inc si              ;ds:si指向下一个data段中要显示的字符
            inc di              ;es:di指向显存区下一个字节(下一个字符的ASCII码字节)
            loop show_area_s1   ;内层循环
        mov cx,ds:[bx+5]        ;设置程序暂停循环的次数
        call show_pause         ;调用子程序产生一段时间的暂停
        add di,0A0H             ;设置es:di指向显存区下一行
        mov al,ds:[bx+3] 
        mov ah,0
        sub di,ax 
        sub di,ax
        pop cx                  ;将外层循环的次数pop出栈
        loop show_area_s        ;外层循环

    ;将子程序用到的寄存器都pop出栈
    pop cx
    pop di
    pop es
    pop ax

    ret         ;子程序show_area返回

;功能:产生一段时间的暂停(时间与参数的平方成正比)
;参数:cx 暂停的时间
;返回:无
show_pause:     ;子程序show_pause开始
    push cx     ;将用到的寄存器压入栈
    push ax
    mov ax,cx   ;将接收的时间参数传送给ax,以ax为内层循环的次数
    show_pause_s:
        push cx     ;将外层循环的次数压入栈
        mov cx,ax   ;设置内层循环的参数
        show_pause_s0:
            nop         ;空指令,产生一段时间的暂停
            loop show_pause_s0  ;内层循环
        pop cx      ;将外层循环的次数pop出栈
        loop show_pause_s   ;外层循环
    pop ax      ;将用到的寄存器pop出栈
    pop cx
    ret         ;show_pause子程序返回

;功能:在屏幕指定位置使用指定字符来显示一个烟花
;参数:
    ;ds:si指向参数列表首地址,参数列表含义如下
        ;地址       参数含义
        ; 00        屏幕起始显示位置的行号
        ; 01        屏幕起始显示位置的列号
        ; 02        烟花花柄长度(>1)
        ; 03        字符的属性字节
        ; 04        用于构成烟花的单个字符
        ; 05        烟花花瓣的长度
        ; 06        显示花径每个字符(或花瓣每一组8个字符)后停顿的时间 低字节
        ; 07        显示花径每个字符(或花瓣每一组8个字符)后停顿的时间 高字节
        ; 08        清除花径每个字符后停顿的时间 低字节
        ; 09        清除花径每个字符后停顿的时间 高字节
        ; 0A-2AH    以字单元依次指明要显示字符的相对地址,8个为一组
;返回:无
show_fireworks:     ;子程序show_fireworks开始
    ;将子程序用到的寄存器压入栈
    push ax
    push es
    push bx
    push cx
    push si
    push di

    ;设置es段指向显示缓冲区段
    mov ax,0B800H   
    mov es,ax

    ;设置es:di指向显示缓冲区指定显示位置的首地址
    mov al,0A0H                 ;每行80个字符,每个字符占2字节,所以每行共80*2=160=A0H个字节
    mul byte ptr ds:[si+0]      ;计算指定行的起始地址(al * 行号 → ax)
    mov di,ax                   ;将乘法结果存放在di中(di = al * 行号)
    mov al,02H                  ;每个字符占2个字节
    mul byte ptr ds:[si+1]      ;计算目标行在指定位置前的字节数(al * 列号 → ax)
    add di,ax                   ;计算指定显示位置在es段的偏移地址(di + ax → di)

    ;显示烟花的花柄
    mov al,ds:[si+4]      ;获取参数:构成烟花的单个字符
    mov ah,ds:[si+3]    ;获取参数:字符的属性字节
    mov bx,di           ;使用bx暂存di,es:di指向显示起始位置
    mov ch,0        ;获取参数:花柄长度
    mov cl,ds:[si+2]    ;以花柄长度作为循环次数
    show_fireworks_s1:  ;显示花柄
        mov es:[di],al   ;将字符传送到显存区
        mov es:[di+1],ah    ;将字符的属性字节传送到显存
        push cx
        mov cx,ds:[si+6]    ;设置程序暂停子程序的时间参数
        call show_pause ;调用子程序,使程序暂停一会儿
        pop cx
        sub di,0A0H         ;ds:di指向行号-1的地址
        loop show_fireworks_s1  ;循环结束,花柄显示完毕

    ;清除烟花的花柄
    mov al,' '           ;要显示的字符是一个空格
    mov ah,0             ;字符的属性字节
    mov ch,0        ;获取参数:花柄长度
    mov cl,ds:[si+2]    ;以花柄长度-1作为循环次数,保留花心
    dec cl
    mov di,bx           ;从bx中恢复di,es:di指向显示起始位置
    show_fireworks_s2:   ;清除花柄
        mov es:[di],al   ;将空格字符传送到显存区
        mov es:[di+1],ah    ;将字符的属性字节传送到显存
        push cx
        mov cx,ds:[si+8]    ;设置程序暂停子程序的时间参数
        call show_pause ;调用子程序,使程序暂停一会儿
        pop cx
        sub di,0A0H         ;es:di指向行号-1的地址
        loop show_fireworks_s2  ;循环结束,花柄清除完毕
        ;至此es:di指向花心

    ;显示烟花的花瓣(八瓣花)
    mov ah,ds:[si+3]    ;获取参数:字符的属性字节
    mov al,ds:[si+4]    ;获取参数:构成烟花的单个字符
    mov cl,ds:[si+5]    ;获取参数:烟花花瓣的长度
    mov si,offset args_show_fireworks_addr  ;获取参数:相对地址列表首地址
    mov ch,0            ;设置外层循环次数为花瓣的长度
    show_fireworks_s:
        push cx         ;将外层循环次数压入栈
        mov cx,08H       ;设置内层循环的次数为8,硬编码,因为是八瓣花
        show_fireworks_s0:  ;内层循环,显示8个方向的字符
            mov bx,ds:[si]  ;获取参数:显存区字符的相对地址
            mov es:[di+bx],al   ;将字符传送到显存区
            mov es:[di+bx+1],ah ;将字符的属性字节传送到显存区
            add si,2            ;ds:si指向下一个字符显示的相对地址
            loop show_fireworks_s0  ;显示完8个方向的各一个字符
        push cx
        mov cx,0200H    ;设置时间参数
        call show_pause ;调用子层序,使程序暂停一会儿
        pop cx          
        pop cx          ;将外层循环的循环次数pop出栈
        loop show_fireworks_s   ;八瓣花显示完毕

    ;将子程序用到的寄存器pop出来
    pop di
    pop si
    pop cx
    pop bx
    pop es
    pop ax
    ret         ;show_fireworks子程序返回

code ends

end start         ;整个汇编程序结束

  • 27
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值