windows - 典型的蛇游戏。如何跟踪蛇?
问题描述
这个棋盘游戏的目标是吃食物和成长。
在其最基本的形式中,游戏仅使用 3 种颜色:一种用于蛇(一系列相互连接的瓷砖),一种用于食物(随机选择的瓷砖),以及一种用于背景(未占用的瓷砖)。因为蛇一直在移动,所以任何时候蛇的头在哪里都会很明显。不需要任何图形标记。玩家可以通过键盘上的方向键控制蛇,瞄准食物。如果食物被吃掉,蛇会长出一个额外的部分,然后将下一个食物放在板上。如果蛇撞到边界或撞到自己,游戏就结束了!
为了使蛇移动,在“头”侧添加一个新段,并在“尾”侧删除一个现有段。在程序中,我们可以将“头”和“尾”的坐标存储在变量中。更新“头”变量很容易,但我们如何才能明确知道新的“尾”在哪里?那么,我们如何跟踪蛇呢?是否有必要发明一些数据结构?
解决方案
为了跟踪蛇,我们必须记录它所有段的位置。我们可以选择存储实际的 (X,Y) 坐标或连续坐标之间变化的指示。我们可以将此信息存储在视频缓冲区矩阵、我们自己的矩阵或循环缓冲区中。
从视频缓冲矩阵中读取游戏信息。
首先让我们假设使用文本视频模式,其中每个字符单元由一个字符字节(ASCII)和一个属性字节(颜色)表示,使我们能够在 16 种前景色和 16 种背景色之间进行选择。如果前景色和背景色恰好相等,那么我们在那里存储的字符代码就不再重要了。结果输出将始终形成单一颜色的实心矩形。我们可以进行设置,以便当前尾巴所在的瓦片的字符字节记录要移动的方向,以便定位新的尾巴。箭头键的扫描码用于此目的。例如,如果当前尾部位于 (5,8) 并且视频内存中的字符字节保存值 48h ( up),那么新尾部将位于 (5,7)。
除了使用字符字节来存储游戏信息,我们还可以使用属性字节。如果我们再选择 ASCII 32(空格),视频硬件只需要背景色,我们可以使用为前景色保留的 4 位空间来记录我们的游戏信息。同样,如果我们选择 ASCII 219(全块),视频硬件只需要前景色,我们可以使用为背景色预留的 4 位空间来记录我们的游戏信息。
在接下来的演示程序中,游戏板上的每个图块都由 80x25 文本视频模式的视频缓冲区中的 2 个字符单元组成。这将产生方形瓷砖。生成方形图块的更简单方法是使用 40x25 文本视频模式,但事实证明,对于 Microsoft Windows,40x25 模式与使用 80x25 模式的左半部分相同。这无助于获得漂亮的方形瓷砖。
隐藏光标也仅仅是为了在 Microsoft Windows 中运行演示。
; The Snake Game - VRAM (c) 2021 Sep Roland
ORG 256
MODE=03h
COLS=80
ROWS=25
SLEN=COLS/8 ; Initial length of snake
MIDP=((ROWS-1)/2)*256+(COLS/2) ; Center of playfield
BACKCOLOR=66h ; Brown
FOODCOLOR=55h ; Magenta
SNAKECOLOR=22h ; Green
TIMER equ gs:046Ch ; BIOS.TimerTick
STRUC Snake a, b, c, d, e
{
.Head dw a
.Tail dw b
.Length dw c
.Flow db d
.Speed db e
}
cld
xor ax, ax
mov gs, ax
mov ax, 0B800h
mov es, ax ; VRAM
mov ax, [TIMER] ; Seed
mov [Rand], ax
mov ax, MODE ; BIOS.SetVideoMode
int 10h
mov dx, ROWS*256+0 ; Hide cursor
mov bh, 0
mov ah, 02h ; BIOS.SetCursor
int 10h
; Paint the playfield, draw the snake and food
xor di, di
mov cx, COLS*(ROWS-1)
mov ax, BACKCOLOR*256+0 ; 0 is free
rep stosw
mov di, (((ROWS-1)/2)*COLS+(COLS/2)-SLEN)*2
mov cx, SLEN*2
mov ax, SNAKECOLOR*256+4Dh
rep stosw
call NewFood ; -> (AX..DX)
; Show "GO" and wait for a keypress, then begin
mov dword [es:((ROWS-1)*COLS+4)*2], 0A4F0A47h
mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
call Status ; -> (AX..DX)
Main: mov ax, [TIMER] ; Sync with real time
@@: cmp ax, [TIMER]
je @b
.kbd: mov ah, 01h ; BIOS.TestKey
int 16h ; -> AX ZF
jz .show
mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
cmp al, 27 ; <ESC>
je Quit
cmp al, 32 ; <SPC>
jne .arrow
.speed: mov al, 11111111b ; Fast uses every tick
cmp [S.Speed], al
jne @f
mov al, 00010001b ; Slow uses one out of four ticks
@@: mov [S.Speed], al
jmp .show
.arrow: mov al, ah
cmp al, 4Dh ; <RIGHT>
je @f
cmp al, 48h ; <UP>
je @f
cmp al, 4Bh ; <LEFT>
je @f
cmp al, 50h ; <DOWN>
jne .show
@@: mov [S.Flow], al ; AL={4Dh=X+, 48h=Y-, 4Bh=X-, 50h=Y+}
.show: ror [S.Speed], 1
jnc Main
mov al, [S.Flow] ; {4Dh,48h,4Bh,50h}
mov cx, [S.Head]
call NextXY ; -> CX
call ReadPlayfieldCell ; -> AL={0,1,4Dh,48h,4Bh,50h} (BX)
cmp al, 1
je .eat ; 0 is free, 1 is food
ja DEAD ; other is snake
.move: call NewHead ; -> (AX..CX)
call NewTail ; -> (AX..CX)
jmp Main
.eat: call NewHead ; -> (AX..CX)
inc [S.Length]
call Status ; -> (AX..DX)
call NewFood ; -> (AX..DX)
jmp Main
; ----------------------------------
; Show "GAME OVER" and wait for <ESC>, then quit
DEAD: mov si, Msg
mov di, ((ROWS-1)*COLS+(COLS/2)-4)*2
lodsw ; First char and color
@@: stosw
lodsb
cmp al, 0
jne @b
@@: mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
cmp al, 27 ; <ESC>
jne @b
; --- --- --- --- --- ---
Quit: mov ax, 0003h ; BIOS.SetVideoMode
int 10h
mov ax, 4C00h ; DOS.Terminate
int 21h
; ----------------------------------
; IN (al,cx) OUT (cx)
NextXY: cmp al, 4Dh ; AL={4Dh,48h,4Bh,50h}
jne @f
add cl, 2 ; 2 character cells per playfield cell
cmp cl, COLS
je DEAD
ret
@@: cmp al, 4Bh ; AL={48h,4Bh,50h}
jae @f
sub ch, 1
jb DEAD
ret
@@: ja @f
sub cl, 2
jb DEAD
ret
@@: add ch, 1
cmp ch, ROWS-1
je DEAD
ret
; ----------------------------------
; IN (cx) OUT () MOD (ax..cx)
NewHead:mov al, [S.Flow]
mov ah, SNAKECOLOR
call WritePlayfieldCell ; -> (BX)
xchg cx, [S.Head]
; --- --- --- --- --- ---
; IN (ax,cx) OUT () MOD (bx)
WritePlayfieldCell:
movzx bx, ch ; CH is Row
imul bx, COLS
add bl, cl ; CL is Column
adc bh, 0
shl bx, 1
mov [es:bx], ax
mov [es:bx+3], ah
ret
; ----------------------------------
; IN () OUT () MOD (ax..cx)
NewTail:mov cx, [S.Tail]
call ReadPlayfieldCell ; -> AL={4Dh,48h,4Bh,50h} (BX)
call NextXY ; -> CX
xchg cx, [S.Tail]
mov ax, BACKCOLOR*256+0 ; 0 is free
jmp WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
NewFood:mov ax, [Rand]
imul ax, 25173
add ax, 13849
mov [Rand], ax
mov bx, ROWS-1
xor dx, dx
div bx
mov ch, dl
mov ax, [Rand]
mov bx, COLS/2
xor dx, dx
div bx
shl dl, 1
mov cl, dl
call ReadPlayfieldCell ; -> AL={0,1,4Dh,48h,4Bh,50h} (BX)
cmp al, 0 ; 0 is free
jne NewFood
mov ax, FOODCOLOR*256+1 ; 1 is food
jmp WritePlayfieldCell
; ----------------------------------
; IN (cx) OUT (al) MOD (bx)
ReadPlayfieldCell:
movzx bx, ch ; CH is Row
imul bx, COLS
add bl, cl ; CL is Column
adc bh, 0
shl bx, 1
mov al, [es:bx]
ret
; ----------------------------------
; IN () OUT () MOD (ax..dx)
Status: mov ax, [S.Length]
mov bx, ((ROWS-1)*COLS+5)*2
mov cx, 10
@@: xor dx, dx
div cx
add dx, 0F00h+'0'
mov [es:bx], dx
sub bx, 2
test ax, ax
jnz @b
mov byte [es:bx], ' '
ret
; ----------------------------------
Msg db 'G', 12, 'AME OVER', 0
ALIGN 2
S Snake MIDP+SLEN-2, MIDP-SLEN, SLEN, 4Dh, 00010001b
Rand dw ?
从我们自己的矩阵中读取游戏信息。
此解决方案类似于读取视频缓冲区矩阵,但更快且更灵活。速度更快,因为从 VRAM 读取比从常规 RAM 读取慢,并且更灵活,因为屏幕可以继续显示所有字符和所有颜色组合。从某种角度来说“更快”:“MATRIX”程序在 1.1 微秒内运行一个典型周期,而“VRAM”程序在 2.6 微秒内运行一个周期。这有关系吗?并非如此,这两个程序都将 99.99% 的时间花在了必要的延迟循环中。
因为内存不缺,我们可以浪费一些,从中受益。即使游戏板的列数较少,我们也可以将矩阵设置为 256 列。如果我们随后将 X 存储在地址寄存器的低字节中,BX
并将 Y 存储在同一地址寄存器的高字节中,则奖励将是无需转换即可获得BX
矩阵内的偏移地址。
; The Snake Game - MATRIX (c) 2021 Sep Roland
ORG 256
MODE=03h
COLS=80
ROWS=25
SLEN=COLS/8 ; Initial length of snake
MIDP=((ROWS-1)/2)*256+(COLS/2) ; Center of playfield
BACKCOLOR=66h ; Brown
FOODCOLOR=55h ; Magenta
SNAKECOLOR=22h ; Green
TIMER equ gs:046Ch ; BIOS.TimerTick
STRUC Snake a, b, c, d, e
{
.Head dw a
.Tail dw b
.Length dw c
.Flow db d
.Speed db e
}
cld
xor ax, ax
mov gs, ax
mov ax, 0B800h
mov es, ax ; VRAM
mov ax, [TIMER] ; Seed
mov [Rand], ax
mov ax, MODE ; BIOS.SetVideoMode
int 10h
mov dx, ROWS*256+0 ; Hide cursor
mov bh, 0
mov ah, 02h ; BIOS.SetCursor
int 10h
; Paint the playfield and matrix, draw the snake and food
xor di, di
mov cx, COLS*(ROWS-1)
mov ax, BACKCOLOR*256+0 ; 0 is free
rep stosw
mov bx, 256*(ROWS-1)
@@: dec bx
mov [Mat+bx], al
jnz @b
mov bx, MIDP-SLEN ; TailXY
mov ax, SNAKECOLOR*256+4Dh
@@: call WritePlayfieldCell ; -> (CX)
add bl, 2 ; X+
cmp bl, (COLS/2)+SLEN-2 ; HeadX
jbe @b
call NewFood ; -> (AX..DX)
; Show "GO" and wait for a keypress, then begin
mov dword [es:((ROWS-1)*COLS+4)*2], 0A4F0A47h
mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
call Status ; -> (AX..DX)
Main: mov ax, [TIMER] ; Sync with real time
@@: cmp ax, [TIMER]
je @b
.kbd: mov ah, 01h ; BIOS.TestKey
int 16h ; -> AX ZF
jz .show
mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
cmp al, 27 ; <ESC>
je Quit
cmp al, 32 ; <SPC>
jne .arrow
.speed: mov al, 11111111b ; Fast uses every tick
cmp [S.Speed], al
jne @f
mov al, 00010001b ; Slow uses one out of four ticks
@@: mov [S.Speed], al
jmp .show
.arrow: mov al, ah
cmp al, 4Dh ; <RIGHT>
je @f
cmp al, 48h ; <UP>
je @f
cmp al, 4Bh ; <LEFT>
je @f
cmp al, 50h ; <DOWN>
jne .show
@@: mov [S.Flow], al ; AL={4Dh=X+, 48h=Y-, 4Bh=X-, 50h=Y+}
.show: ror [S.Speed], 1
jnc Main
mov al, [S.Flow] ; {4Dh,48h,4Bh,50h}
mov bx, [S.Head]
call NextXY ; -> BX
cmp byte [Mat+bx], 1
je .eat ; 0 is free, 1 is food
ja DEAD ; other is snake
.move: call NewHead ; -> (AX..CX)
call NewTail ; -> (AX..CX)
jmp Main
.eat: call NewHead ; -> (AX..CX)
inc [S.Length]
call Status ; -> (AX..DX)
call NewFood ; -> (AX..DX)
jmp Main
; ----------------------------------
; Show "GAME OVER" and wait for <ESC>, then quit
DEAD: mov si, Msg
mov di, ((ROWS-1)*COLS+(COLS/2)-4)*2
lodsw ; First char and color
@@: stosw
lodsb
cmp al, 0
jne @b
@@: mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
cmp al, 27 ; <ESC>
jne @b
; --- --- --- --- --- ---
Quit: mov ax, 0003h ; BIOS.SetVideoMode
int 10h
mov ax, 4C00h ; DOS.Terminate
int 21h
; ----------------------------------
; IN (al,bx) OUT (bx)
NextXY: cmp al, 4Dh ; AL={4Dh,48h,4Bh,50h}
jne @f
add bl, 2 ; 2 character cells per playfield cell
cmp bl, COLS
je DEAD
ret
@@: cmp al, 4Bh ; AL={48h,4Bh,50h}
jae @f
sub bh, 1
jb DEAD
ret
@@: ja @f
sub bl, 2
jb DEAD
ret
@@: add bh, 1
cmp bh, ROWS-1
je DEAD
ret
; ----------------------------------
; IN (al,bx) OUT () MOD (ax..cx)
NewHead:xchg bx, [S.Head]
mov [Mat+bx], al
mov ah, SNAKECOLOR
mov bx, [S.Head]
; --- --- --- --- --- ---
; IN (ax,bx) OUT () MOD (cx)
WritePlayfieldCell:
mov [Mat+bx], al
movzx cx, bh ; BH is Row
imul cx, COLS
add cl, bl ; BL is Column
adc ch, 0
shl cx, 1
xchg bx, cx
mov [es:bx+1], ah
mov [es:bx+3], ah
mov bx, cx
ret
; ----------------------------------
; IN () OUT () MOD (ax..cx)
NewTail:mov bx, [S.Tail]
mov al, [Mat+bx] ; -> AL={4Dh,48h,4Bh,50h}
call NextXY ; -> BX
xchg bx, [S.Tail]
mov ax, BACKCOLOR*256+0 ; 0 is free
jmp WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
NewFood:mov ax, [Rand]
imul ax, 25173
add ax, 13849
mov [Rand], ax
mov cx, ROWS-1
xor dx, dx
div cx
mov bh, dl
mov ax, [Rand]
mov cx, COLS/2
xor dx, dx
div cx
shl dl, 1
mov bl, dl
cmp byte [Mat+bx], 0 ; 0 is free
jne NewFood
mov ax, FOODCOLOR*256+1 ; 1 is food
jmp WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
Status: mov ax, [S.Length]
mov bx, ((ROWS-1)*COLS+5)*2
mov cx, 10
@@: xor dx, dx
div cx
add dx, 0F00h+'0'
mov [es:bx], dx
sub bx, 2
test ax, ax
jnz @b
mov byte [es:bx], ' '
ret
; ----------------------------------
Msg db 'G', 12, 'AME OVER', 0
ALIGN 2
S Snake MIDP+SLEN-2, MIDP-SLEN, SLEN, 4Dh, 00010001b
Rand rw 1
Mat rb 256*(ROWS-1)
从环形缓冲区读取实际坐标。
在这个循环缓冲区中,我们记录了从头到尾的所有蛇段的坐标。缓冲区的大小必须能够容纳可能(或规则允许的)最长的蛇。该程序存储指向第一条记录(Head)和最后一条记录后面(Tail)的指针。对于新的蛇头,我们降低Head指针并插入新坐标。对于新的蛇尾,我们只需降低Tail指针,丢弃最后一条记录。
因为我们需要保持在环形缓冲区的内存范围内,所以需要一种环绕机制。为 ringbuffer 的内存选择 2 的幂大小很重要,因为这样我们就可以通过简单的AND
指令进行回绕。如果我们选择这个 2 的幂大小为 65536,那么我们可以AND
完全放弃这个操作,因为 CPU 在实际地址模式下已经自动回绕到 64KB。
搜索 ringbuffer 需要时间,而且这个时间必然会随着蛇的变长而增加。但是,在一个程序中,出于可玩性的原因,超过 99% 的时间都花在了延迟循环中,这并不重要!
; The Snake Game - RINGBUFFER (c) 2021 Sep Roland
ORG 256
MODE=03h
COLS=80
ROWS=25
SLEN=COLS/8 ; Initial length of snake
MIDP=((ROWS-1)/2)*256+(COLS/2) ; Center of playfield
BACKCOLOR=66h ; Brown
FOODCOLOR=55h ; Magenta
SNAKECOLOR=22h ; Green
TIMER equ gs:046Ch ; BIOS.TimerTick
STRUC Snake a, b, c, d, e
{
.Head dw a
.Tail dw b
.Length dw c
.Flow db d
.Speed db e
}
cld
xor ax, ax
mov gs, ax
mov ax, cs
add ax, (EOF+15)/16
mov ss, ax ; 512 bytes stack
mov sp, 512
add ax, 512/16
mov fs, ax ; 64KB ringbuffer
mov ax, 0B800h
mov es, ax ; VRAM
mov ax, [TIMER] ; Seed
mov [Rand], ax
mov ax, MODE ; BIOS.SetVideoMode
int 10h
mov dx, ROWS*256+0 ; Hide cursor
mov bh, 0
mov ah, 02h ; BIOS.SetCursor
int 10h
; Paint the playfield, draw the snake and food
xor di, di
mov cx, COLS*(ROWS-1)
mov ax, BACKCOLOR*256+0
rep stosw
mov cx, MIDP-SLEN ; HeadXY==TailXY
@@: call NewHead ; -> (AX..BX)
add cl, 2 ; X+
cmp cl, (COLS/2)+SLEN-2 ; HeadX
jbe @b
call NewFood ; -> (AX..DX)
; Show "GO" and wait for a keypress, then begin
mov dword [es:((ROWS-1)*COLS+4)*2], 0A4F0A47h
mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
call Status ; -> (AX..DX)
Main: mov ax, [TIMER] ; Sync with real time
@@: cmp ax, [TIMER]
je @b
.kbd: mov ah, 01h ; BIOS.TestKey
int 16h ; -> AX ZF
jz .show
mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
cmp al, 27 ; <ESC>
je Quit
cmp al, 32 ; <SPC>
jne .arrow
.speed: mov al, 11111111b ; Fast uses every tick
cmp [S.Speed], al
jne @f
mov al, 00010001b ; Slow uses one out of four ticks
@@: mov [S.Speed], al
jmp .show
.arrow: mov al, ah
cmp al, 4Dh ; <RIGHT>
je @f
cmp al, 48h ; <UP>
je @f
cmp al, 4Bh ; <LEFT>
je @f
cmp al, 50h ; <DOWN>
jne .show
@@: mov [S.Flow], al ; AL={4Dh=X+, 48h=Y-, 4Bh=X-, 50h=Y+}
.show: ror [S.Speed], 1
jnc Main
mov al, [S.Flow] ; {4Dh,48h,4Bh,50h}
mov bx, [S.Head]
mov cx, [fs:bx]
call NextXY ; -> CX
cmp cx, [FoodXY]
je .eat
call ScanSnake ; -> ZF (BX)
jnz DEAD ; CX is (X,Y) of some snake part
.move: call NewHead ; -> (AX BX)
call NewTail ; -> (AX..CX)
jmp Main
.eat: call NewHead ; -> (AX BX)
inc [S.Length]
call Status ; -> (AX..DX)
call NewFood ; -> (AX..DX)
jmp Main
; ----------------------------------
; Show "GAME OVER" and wait for <ESC>, then quit
DEAD: mov si, Msg
mov di, ((ROWS-1)*COLS+(COLS/2)-4)*2
lodsw ; First char and color
@@: stosw
lodsb
cmp al, 0
jne @b
@@: mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
cmp al, 27 ; <ESC>
jne @b
; --- --- --- --- --- ---
Quit: mov ax, 0003h ; BIOS.SetVideoMode
int 10h
mov ax, 4C00h ; DOS.Terminate
int 21h
; ----------------------------------
; IN (al,cx) OUT (cx)
NextXY: cmp al, 4Dh ; AL={4Dh,48h,4Bh,50h}
jne @f
add cl, 2 ; 2 character cells per playfield cell
cmp cl, COLS
je DEAD
ret
@@: cmp al, 4Bh ; AL={48h,4Bh,50h}
jae @f
sub ch, 1
jb DEAD
ret
@@: ja @f
sub cl, 2
jb DEAD
ret
@@: add ch, 1
cmp ch, ROWS-1
je DEAD
ret
; ----------------------------------
; IN (cx) OUT (ZF) MOD (bx)
ScanSnake:
mov bx, [S.Tail]
mov [fs:bx], cx ; Sentinel
mov bx, [S.Head]
sub bx, 2
@@: add bx, 2
cmp [fs:bx], cx
jne @b
cmp bx, [S.Tail]
ret
; ----------------------------------
; IN (cx) OUT () MOD (ax,bx)
NewHead:mov bx, -2
xadd [S.Head], bx
mov [fs:bx-2], cx
mov ah, SNAKECOLOR
; --- --- --- --- --- ---
; IN (ah,cx) OUT () MOD (bx)
WritePlayfieldCell:
movzx bx, ch ; CH is Row
imul bx, COLS
add bl, cl ; CL is Column
adc bh, 0
shl bx, 1
mov [es:bx+1], ah
mov [es:bx+3], ah
ret
; ----------------------------------
; IN () OUT () MOD (ax..cx)
NewTail:mov bx, -2
xadd [S.Tail], bx
mov cx, [fs:bx-2]
mov ah, BACKCOLOR
jmp WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
NewFood:mov ax, [Rand]
imul ax, 25173
add ax, 13849
mov [Rand], ax
mov bx, ROWS-1
xor dx, dx
div bx
mov ch, dl
mov ax, [Rand]
mov bx, COLS/2
xor dx, dx
div bx
shl dl, 1
mov cl, dl
call ScanSnake ; -> ZF (BX)
jnz NewFood ; CX is (X,Y) of some snake part
mov [FoodXY], cx
mov ah, FOODCOLOR
jmp WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
Status: mov ax, [S.Length]
mov bx, ((ROWS-1)*COLS+5)*2
mov cx, 10
@@: xor dx, dx
div cx
add dx, 0F00h+'0'
mov [es:bx], dx
sub bx, 2
test ax, ax
jnz @b
mov byte [es:bx], ' '
ret
; ----------------------------------
Msg db 'G', 12, 'AME OVER', 0
ALIGN 2
S Snake SLEN*2, SLEN*2, SLEN, 4Dh, 00010001b
FoodXY dw ?
Rand dw ?
EOF:
推荐阅读
- android - 在android中正确隐藏状态栏
- python - 如何通过裁剪两个图像中不需要的区域来调整蒙版和 RGB 图像的大小以匹配
- excel - 最少原材料,最多套数
- visual-studio-code - 无法打开“defaultSettings.json”:无效的正则表达式:/*.xml/:没有可重复的内容
- swift - Webrtc Swift - 线程 1:EXC_BAD_ACCESS(代码=1,地址=0xd000000000000040)
- visual-studio-code - 如何在 VSCode 扩展中检测来自外部的文件更改?
- postgresql - 如何修复 psql 中“CREATE”处或附近的语法错误
- android - 如何使用 github 操作构建 opencv
- angular - 在 Angular 材料中,我想计算两个 mat-cell 输入值并将其显示在另一个 mat-cell 中(在动态添加的列中)
- reactjs - 如何在使用 axios 在 React JS 中预览后发送数据?