用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         ;整个汇编程序结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/375220.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

日本承认Omotenashi任务失败

日本在征服月球的尝试失败后承认失败 25.11.2022 日本已经取消了成为第四个登上月球的国家的申请。作为阿尔忒弥斯一号任务的一部分,日本宇宙航空研究开发机构(JAXA)将其Omotenashi CubeSat与NASA的SLS火箭和猎户座飞船一起送上了月球。但在…

人生,总要读几本好书!

以前,没有重视过读书的重要性 但是自从进入老马的陪伴群之后,听了老马的一路成长经历,才发现,所谓的一鸣惊人,都是厚积薄发的表现 大佬们在出人头地之前,都是有过很长一段时间的自我提升的 这个提升的方…

【数据库】创建索引的注意事项

🍎个人博客:个人主页 🏆个人专栏:数据库 ⛳️ 功不唐捐,玉汝于成 目录 前言 正文 结语 我的其他博客 前言 在数据库设计和优化中,索引的合理使用是提高查询性能和加速数据检索的关键因素之一。通过选…

浅析现代计算机启动流程

文章目录 前言启动流程概述磁盘分区格式MBR磁盘GPT磁盘隐藏分区 传统BIOS引导传统BIOS启动流程 UEFI引导UEFI引导程序UEFI启动流程 引导加载程序启动操作系统相关参考 前言 现代计算机的启动是一个漫长的流程,这个流程中会涉及到各种硬件的配置与交互,包…

《C程序设计》上机实验报告(六)之函数及其应用

实验内容&#xff1a; 1.运行程序 #include <stdio.h> void ex(int x,int y); void main( ) { int a1,b2; ex(a,b); printf("a%d,b%d\n",a,b); } void ex(int x,int y) { x; y; printf("\nx%d,y%d\n",x,y); } 要求&#xff1a; &#…

202418读书笔记|《成功的聪明人太多了,我必须为笨蛋争一口气》——做精致有趣的你呀

202418读书笔记|《成功的聪明人太多了&#xff0c;我必须为笨蛋争一口气》——做精致有趣的你呀 《成功的聪明人太多了&#xff0c;我必须为笨蛋争口气》书单狗一如既往的搞笑&#xff0c;幽默&#xff0c;博学。狗生哲学&#xff1a;做精致有趣的你呀。 趁着付费会员卡的劲儿&…

数据结构——框架简介

1.数据结构的作用 数据结构是计算机科学中一种重要的概念&#xff0c;它主要用于组织和存储数据以便有效地进行操作。数据结构可以看作是数据的组织方式&#xff0c;通过合理的数据结构设计&#xff0c;可以更高效地执行各种操作&#xff0c;提高程序的性能和可维护性。 以下是…

PyTorch 2.2 中文官方教程(十九)

使用 RPC 进行分布式管道并行 原文&#xff1a;pytorch.org/tutorials/intermediate/dist_pipeline_parallel_tutorial.html 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 作者&#xff1a;Shen Li 注意 在github中查看并编辑本教程。 先决条件&#xff1a; PyTorc…

【Jenkins】pipeline基本使用

目录 一、pipeline 二、创建pipeline项目 1、安装pipeline插件 2、创建pipeline项目 三、pipeline语法 1、pipeline组成 2、agent&#xff1a;指定流水线的执行位置&#xff0c;流水线中每个阶段都必须在某个地方执行 3、stage&#xff1a;阶段&#xff0c;代表流水线的…

2024 高级前端面试题之 框架通识 「精选篇」

该内容主要整理关于 框架通识 的相关面试题&#xff0c;其他内容面试题请移步至 「最新最全的前端面试题集锦」 查看。 框架通识精选篇 1. MVVM2. 路由原理3. Virtual Dom3.1 为什么需要 Virtual Dom3.2 Virtual Dom 算法简述3.2 Virtual Dom 算法实现 4. Diff算法4.1 React-Di…

【MySQL】学习如何使用DCL进行用户管理

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-JwFD16F1Kh0fle0X {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

五、医学影像云平台 - 医共体

原创不易&#xff0c;多谢关注&#xff01;谢谢&#xff01; 1. 医学大影像设备市场现状 目前影像设备&#xff0c;可以说低端产品同质化越来越严重&#xff0c;利润越来越薄&#xff0c;而高端超高端设备&#xff0c;整体销售额却在增长&#xff0c;利润空间也比低端的要高的…

行业应用科普 | 患者护理应用连接器

【摘要/前言】 通过医疗专业人士为患者提供护理的种种需求&#xff0c;已经不限于手术室与医院的各种安全状况。当今许多患者的护理都是在其他环境进行&#xff0c;例如医生办公室、健康中心&#xff0c;还有越来越普遍的住家。尤其是需要长期看护的患者&#xff0c;所需的科技…

PCB经验规则的综合应用

PCB经验规则的综合应用 走线尽量短&#xff0c;长度小于信号波长的十分之一 二是无法短的&#xff0c;就控制它的阻抗 按传输线设计、控制阻抗 首先我们来看看电路板的参数。常见的1.6毫米电路板 1oz 铜箔&#xff0c;介质 FR4&#xff0c;介电常数4.6-4.8&#xff0c;板芯厚…

编译原理与技术(三)——语法分析(四)自底向上-移进归约

一、语法分析的主要方法 二、归约 三、句柄&#xff08;可归约串&#xff09; 归约涉及到一个问题。 对于输入串&#xff0c;我们怎么知道哪一部分可以被归约&#xff1f; 我们定义&#xff0c;可以被归约的子串称为句柄。 显然&#xff0c;句柄可能不是唯一的。 四、移进-…

CTF-show WEB入门--web17

今日完成web12,老规矩先看看题目提示&#xff1a; 我们可以看到题目提示为&#xff1a; 备份的sql文件会泄露敏感信息 然后我们再打开题目链接; 然后我们查看页面内容和网页源代码&#xff0c;什么有用的信息都没有得到&#xff1a; 根据题目提示为敏感信息泄露&#xff0c;那…

解析与模拟常用字符串函数strcpy,strcat,strcmp,strstr(一)

今天也是去学习了一波字符串函数&#xff0c;想着也为了加深记忆&#xff0c;所以写一下这篇博客。既帮助了我也帮助了想学习字符串函数的各位。下面就开始今天的字符串函数的学习吧。 目录 strcpy与strncpy strcat与strncat strcmpy strstr strcpy与strncpy 在 C 语言中&…

机器学习系列6-逻辑回归

重点&#xff1a; 1.逻辑回归模型会生成概率。 2. 对数损失是逻辑回归的损失函数。 3. 逻辑回归被许多从业者广泛使用。 # 1.逻辑回归&#xff1a;计算概率 **许多问题需要将概率估算值作为输出。逻辑回归是一种非常高的概率计算机制。** 实际上&#xff0c;您可以通过以下两种…

Fart12配套批量修复二代函数抽取壳工具发布

旧版Fart12定制版jadx的弊端 之前寒冰大佬推出的Fart12定制版jadx可以将Fart12脱下来的dex文件与主动调用过程中拿到的函数体bin文件自动重构与合并&#xff0c;修复并合并成新的dex。 修复完之后dex文件大小也会变大&#xff0c;且函数体也由原来的nop变成了真正的函数逻辑代…

算法练习-二叉搜索树的最小绝对差(思路+流程图+代码)

难度参考 难度&#xff1a;中等 分类&#xff1a;二叉树 难度与分类由我所参与的培训课程提供&#xff0c;但需要注意的是&#xff0c;难度与分类仅供参考。且所在课程未提供测试平台&#xff0c;故实现代码主要为自行测试的那种&#xff0c;以下内容均为个人笔记&#xff0c;旨…