一 需求分析
根据以下几部分来实现打字练习:
- 随机显示字母,字母出现的位置随机
- 字母自动落下
- 从键盘输入的字母与落下字母相同则该字母消失,否则字母自动接着落下
- 按下“Esc”键则程序返回主菜单
- 字母下落过程中按空格键暂停
- 在主界面按“E”则程序退出
打字练习的主要功能由以上六部分组成,每一部分之间的联系都是比较紧密的。对于以上及部分,最主要的部分就是中间的四个部分,这是打字练习的重点,需要详细设计其所需要的功能。
二 程序设计
主模块是打字游戏的核心模块,主要通过各个键盘符来控制各个子模块之间的协调,完成打字游戏的运行。
子模块主要包括:初始化子模块、速度设定子模块、显示时钟子模块、开始打字子模块,显示打字结果子模块。
- 初始化子模块包括显示初始界面菜单,初始化程序参数,判断是否进入游戏
- 速度设定子模块包括速度选择子程序和速度设置子程序
- 显示时钟子模块包括取系统时钟和显示两个子程序
- 开始打字子模块包括显示分数子程序,当敲入字符与下落相符时扬声器发声子程序,字母下落子程序,产生新的字母和新的位置子程序,延时子程序。这些程序有机的组合在一起,完成整个指法练习的程序
初始化子模块包括初始化程序参数,显示初始界面菜单,判断是否进入游戏。首先初始化字母出现的位置,初始化得分和各种标志的值,然后显示初始界面菜单,通过一个比较指令和堆栈操作来判断是否进入游戏。
2.1 系统总体框架
2.2 系统流程图
三 程序实现
3.1 实现环境
- 硬件环境:IBM-PC机,硬盘40G以上,内存256M以上,打印机等
- 软件环境:Windows 2000 Server或Windows XPServer操作系统,TC,QE等编辑软件,MASM汇编软件
3.2 关键代码说明
Init_game macro op1,op2,op3,op4,op5 local ns mov cx,00h mov dh,op1 mov dl,op2 ns:mov ah,02h;设置光标位置 mov bh,00h;页号为0 int 10h push cx mov ah,0ah;在当前光标位置写字符 mov al,op3;al=字符的ascii码 mov bh,00h;bh=页号bl=字符属性 mov cx,01h;cx=写入字符重复次数 int 10h pop cx;cx=0 inc cx;cx=cx+1 inc op4 cmp cx,op5 jne ns endm clear_screen macro op1,op2,op3,op4 ;清屏宏定义 cx,屏幕的左上角,dx屏幕的右下角 mov ah,06h mov al,00h mov bh,0eh;改变行属性的色彩,字的色彩,bh空白行的属性/07就是正常的黑底白字 mov ch,op1 mov cl,op2 mov dh,op3 mov dl,op4 int 10h mov ah,02h;设置光标的位置从0000开始 mov bh,00h mov dh,00h mov dl,00h int 10h endm menu macro op1,op2,op3 ;菜单显示宏定义 mov ah,02h mov bh,00h mov dh,op1 mov dl,op2 int 10h mov ah,09h lea dx,op3 int 21h endm data segment ZK db "WELCOME TO PLAY$" no db "date:2013/12/27$" meg db "press Enter key to continue.......$" meg1 db "when a letter is dropping,please hit it!$" meg2 db "press space key to pause!$" meg3 db "press ESC key to return main interface!$" meg4 db "press letter 'E' to exit!$" speed dw 600d letters_bak db "jwmilzoeucgpravskntxhdyqfb" db "iytpkwnxlsvxrmofzhgaebudjq" db "nwimzoexrphysfqtvdcgljukda" letters db 78d dup(0) letter_counter db 0 life_flag db 78 dup(0) position_flag db 78 dup(0) present_position db 1 data ends stack segment para stack 'stack' db 64 dup(0) stack ends code segment main proc far assume cs:code,ds:data,ss:stack start: mov ax,data mov ds,ax mov letter_counter,00h mov present_position,1 lea si,position_flag; mov ah,00h mov cx,00h lea di,letters;di的偏移地址为letters lea si,letters_bak;si的偏移地址为letter_bak mov cx,00h;cx=0 init_letters: mov ah,[si];ah=j mov [di],ah;ah的值放到letters里面;letters_bak的值放入letters里面 inc si;si+1 inc di;di+1 inc cx;cx+1 cmp cx,78d; jne init_letters;不为0就到init_letters,一直循环到letters里 mov ah,00h lea si,life_flag; mov cx,00h init_life_flag: mov [si],ah inc si inc cx cmp cx,78d jne init_life_flag mov cx,00h ;ch=光标开始行,cl=光标结束行 根据CX给出光标的大小 mov ah,01h or ch,00010000b;ch>20h,光标消失,cl>20h,覆盖字符 int 10h clear_screen 00d,00d,24d,79d ;清屏,0000- 2479 Init_game 00d,00d,0ah,dl,80d ;这个四个是初始化屏幕的上下左右的框框 Init_game 24d,00d,0ah,dl,80d Init_game 00d,00d,0ah,dh,25d Init_game 00d,79d,0ah,dh,25d menu 05d,15d,ZK ;菜单信息的宏调用,这五行是在屏幕上显示提示消息 menu 07h,15d,no menu 09d,15d,meg menu 11d,15d,meg1 menu 13d,15d,meg2 menu 15d,15d,meg3 menu 17d,15d,meg4 put: mov ah,02h ;设置光标位置 mov bh,00h;设置页码 mov dh,22d;dx行列坐标 mov dl,33d int 10h mov ah,01h ;从键盘输入任意字符并回显示,al=输入字符 int 21h cmp al,0dh;是否为换行符 je speed3;如果是换行符则跳转到speed3处 cmp al,45h;比较是否为e je exit;如果为e,转到exit exit: mov ah,4ch int 21h speed3: mov ax,speed+12 mov speed,ax jmp begin begin: clear_screen 01d,01d,23d,78d ;清屏宏调用 ; clear_screen 01d,01d,23d,78d Init_game 23d,01d,03h,dl,78d;23d01d行列坐标,初始化倒数第二行的字符 mov ah,02h mov bh,00h mov dh,01h mov dl,01h int 10h mov cx,00h lea si,letters ;si的偏移地址是letters nextletter: mov ah,02h ;显示字母 mov dl,[si] ;把letters的字符放到dl里 int 21h ;通过dos中断的2号功能项,把字符显示出来 inc si inc cx cmp cx,78d je nextcycle;全部显示完了后,跳到nextcycle jmp nextletter from_front: sub present_position,78d ;当超过78个字时的处理方式 减去78 jmp gobackto_si;跑到gobackto_si这来 find_zero: cmp letter_counter,78d ;letter_counter有78了,初始化 je recycle;如果有跑到recycle cmp present_position,78d;如果present_position等于78d, je from_one mov ah,00h nextsi: add present_position,01h inc si cmp [si],ah je gobackto_di cmp present_position,78d je from_one jmp nextsi from_one:mov present_position,01h ;present_position=01 jmp gobackto_si recycle:mov letter_counter,00h;letter_counter=0 mov present_position,01d;present_position=01 lea si,position_flag;si=position_flag的偏移地址 mov cx,00h mov ah,00h clearsi: mov [si],ah;position_flag地址搞成0 inc cx cmp cx,78d je nextcycle inc si jmp clearsi nextcycle: lea di,letters;di的偏移地址是letters[字母] lea si,position_flag;si的偏移地址是position_flag add present_position,31d;31一跳,这个你可以随便设置 cmp present_position,78d;;超过78个字节 ja from_front gobackto_si: add si,word ptr present_position;si=si+present_position,si向后偏移 dec si; 要不要都无所谓,只不过,因为开始就觉定了是要31一跳,所以这里减一个1位 mov ah,[si];把position_flag放到ah里 cmp ah,01h;看看position_flag里面有没有标志1 je find_zero;如果ah为1转移,否则 gobackto_di: mov ah,01h mov [si],ah add di,word ptr present_position dec di;因为列坐标是从0开始,而字符是从1开始,所以这里是32-1 mov dl,present_position; mov ah,02h mov bh,00h mov dh,01h int 10h mov cx,00h nextrow: push cx mov cx,00h out_cycle: ; 延迟 push cx mov cx,00h in_cycle: add cx,01h cmp cx,1000 ; jne in_cycle ;zf=0转到标号处执行, push dx mov ah,06h ;从键盘输入字符,al等于字符 mov dl,0ffh int 21h pop dx jz pass cmp al,1bh ;如果键入ESC,则返回主菜单 je to_start1 cmp al," " ;如果键入SPACE,则游戏暂停 je pause cmp al,[di] ;输入字母正确!则字母消失 je disappear pass: pop cx inc cx cmp cx,speed je print jmp out_cycle pause: push dx ;暂停处理 第一次知道暂停是这样的,循环空代码 mov ah,06h mov dl,0ffh int 21h pop dx cmp al," " jne pause jmp pass to_start1: ;返回主菜单 jmp start print: mov ah,0ah ;在当前光标位置写空格 mov al," " mov bh,00h mov cx,01h int 10h inc dh mov ah,02h ;改变光标位置 mov bh,00h int 10h mov ah,0ah ;在当前光标位置写字母 mov al,[di] mov bh,00h mov cx,01h int 10h pop cx inc cx cmp cx,21d je print_next_letter jmp nextrow ;下一行 disappear: ;击中字母后输出空格 pop cx pop cx mov ah,0ah;在光标处按原来属性显示字符 mov al," " mov bh,00h mov cx,01h int 10h jmp hit print_next_letter: lea si,life_flag add si,word ptr present_position dec si mov ah,0ah;在当前光标处按原有属性显示字符 mov al," ";最倒数第二排写入字符,意思是当掉下来的字符到倒数第二行的时候,自动变成空格消失 mov bh,00h mov cx,01h int 10h inc dh ;这就是到了最后一行 mov ah,02h;2号中断,设置文本光标位置 mov bh,00h int 10h mov ah,0ah;把最后一行的字符变成空格 mov al," " mov bh,00h mov cx,01h;重复输出,这里的重复输出的意思就是输入一个空格 int 10h mov ah,1;把life_flag变成1,这样下次就可以不在同一个位置掉字符下来 mov [si],ah hit: mov ah,02h;设置光标 mov bh,00h mov dh,01h;第一行 mov dl,present_position;下一个字符的列 int 10h mov al,[di] ; 出现下一个新字母的数法 add al,7;di+7 cmp al,7ah;z的ascii码就是7ah,所以当al大于7ah时转移 ja convey_letter mov ah,0ah;在当前光标按原有属性显示字符,al=字符 mov bh,00h mov cx,01h int 10h mov [di],al add letter_counter,01h;统计次数 jmp nextcycle convey_letter: sub al,7ah add al,61h;al等于要显示的字符,加61表示是小写字母 mov ah,0ah mov bh,00h mov cx,01h int 10h mov [di],al add letter_counter,01h jmp nextcycle clear_screen 01,01,23,78 mov ah,02h mov bh,00h mov dh,11d mov dl,20d int 10h inc dh inc dh mov ah,02h mov bh,00h int 10h notkey: mov ah,07h int 21h cmp al,0dh je to_start cmp al,1bh je over jmp notkey to_start: clear_screen 00,00,24,79 jmp start over: clear_screen 01,01,23,78 mov ah,02h mov bh,00h mov dh,11d mov dl,15h int 10h mov ah,02h mov bh,00h mov dh,13d mov dl,15h int 10h mov ah,07h int 21h mov ah,07h int 21h clear_screen 00,00,24,79 mov ax,4c00h int 21h main endp code ends end start
四 运行测试