Here's a nice sprite designer program that I wrote back in about 2006 I think. It uses sub-programs heavily, as a result I think the code itself is quite easy to read.
40 DATA 120,124,128,132,136,140
50 CALL CLEAR :: CALL MAGNIFY(4):: CALL SCREEN(2):: FOR I=1 TO 14 :: CALL COLOR(I,15,1):: NEXT I :: CALL COLOR(4,5,1)
60 CALL CHAR(92,"F8F0F0F89C0E0703000000000000000000000000000000000000000000000000")
70 A$="AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55"
80 CALL CHAR(88,"08080006C6191B061D336142444A3400003C6282848A54E05CFC60DED8561400")
90 RESTORE 40 :: FOR I=1 TO 6 :: READ A :: CALL CHAR(A,A$):: NEXT I
100 CALL CHARPAT(ASC(":"),A$):: CALL CHAR(ASC("@"),A$)
110 CALL SPRITE(#2,120,15,16,156):: CALL SPRITE(#3,124,15,16,204)
120 CALL SPRITE(#4,128,15,60,156):: CALL SPRITE(#5,132,15,60,204):: CALL SPRITE(#6,136,15,104,156):: CALL SPRITE(#7,140,15,104,204)
130 CALL CHAR(60,"0000001818000000"):: CALL CHAR(62,"FEFEFEFEFEFEFE00")
140 CX=1 :: CY=2 :: OPTION BASE 1 :: DIM C$(16,4),C(16,4):: SZ=1
150 CALL INIT(C$(,),C(,))
160 CALL HELP
170 CALL SPRITE(#1,92,4,(CY*8)-4,(CX*8)+14)
180 CALL KEY(0,K,S):: IF K=-1 THEN 180
190 IF K=101 THEN CY=CY-1 :: IF CY<2 THEN CY=17 ELSE GOTO 170
200 IF K=120 THEN CY=CY+1 :: IF CY>17 THEN CY=2 ELSE GOTO 170
210 IF K=115 THEN CX=CX-1 :: IF CX<1 THEN CX=16 ELSE GOTO 170
220 IF K=100 THEN CX=CX+1 :: IF CX>16 THEN CX=1 ELSE GOTO 170
230 IF K=48 THEN CALL POINT(CY-1,CX,C$(,),C(,))
240 IF K=73 THEN CALL INVERT(C$(,),C(,))
250 IF K=68 THEN CALL DOWN(C$(,),C(,))
260 IF K=85 THEN CALL UP(C$(,),C(,))
270 IF K=76 THEN CALL LEFT(C$(,),C(,))
280 IF K=50 THEN SZ=SZ XOR 1 :: CALL MAGNIFY(3+SZ)
290 IF K=82 THEN CALL RIGHT(C$(,),C(,))
300 IF K=72 THEN CALL HFLIP(C$(,),C(,))
310 IF K=86 THEN CALL VFLIP(C$(,),C(,))
320 IF K=78 THEN CALL SCRATCH(C$(,),C(,))
330 IF K=65 THEN CALL ASSIGN(C$(,),C(,))
340 IF K=71 THEN CALL LOADGRID(C$(,),C(,))
350 IF K=70 THEN CALL FILEMENU(C$(,),C(,))
360 IF K=74 THEN CALL LFSPRITE(C$(,),C(,))
370 IF K=87 THEN CALL ANIMATE(C$(,),C(,))
380 GOTO 170
390 ! #############
400 SUB INIT(C$(,),C(,))
410 CALL BUSY :: DISPLAY AT(1,1):"Sprite designer v1.2 M.Wills"
420 CALL NEW(C$(,),C(,)):: CALL DONE
430 SUBEND
440 ! #############
450 SUB NEW(C$(,),C(,)):: CALL BUSY
460 FOR Y=1 TO 16 :: FOR X=1 TO 4 :: C$(Y,X)="<<<<" :: C(Y,X)=0 :: NEXT X :: NEXT Y
470 CALL DRAWGRID(C$(,),C(,)):: CALL DONE
480 SUBEND
490 ! #############
500 SUB DRAWGRID(C$(,),C(,)):: CALL BUSY
510 FOR Y=1 TO 16 :: XX=1 :: FOR X=1 TO 4 :: DISPLAY AT(Y+1,XX)SIZE(4):C$(Y,X):: XX=XX+4 :: NEXT X :: NEXT Y :: CALL DONE
520 SUBEND
530 ! #############
540 SUB POINT(Y,X,C$(,),C(,))
550 S=INT((X-1)/4)+1 :: P=3-(X-1)AND 3 :: C(Y,S)=C(Y,S)XOR 2^P
560 CALL GETNYBBLE(C(Y,S),N$):: DISPLAY AT(Y+1,((S-1)*4)+1)SIZE(4):N$:: C$(Y,S)=N$
570 SUBEND
580 ! #############
590 SUB GETNYBBLE(V,N$)
600 IF V=0 THEN GOSUB 620 ELSE ON V GOSUB 630,640,650,660,670,680,690,700,710,720,730,740,750,760,770
610 SUBEXIT
620 N$="<<<<" :: RETURN
630 N$="<<<>" :: RETURN
640 N$="<<><" :: RETURN
650 N$="<<>>" :: RETURN
660 N$="<><<" :: RETURN
670 N$="<><>" :: RETURN
680 N$="<>><" :: RETURN
690 N$="<>>>" :: RETURN
700 N$="><<<" :: RETURN
710 N$="><<>" :: RETURN
720 N$="><><" :: RETURN
730 N$="><>>" :: RETURN
740 N$=">><<" :: RETURN
750 N$=">><>" :: RETURN
760 N$=">>><" :: RETURN
770 N$=">>>>" :: RETURN
780 SUBEND
790 ! ################
800 SUB INVERT(C$(,),C(,)):: CALL BUSY
810 FOR Y=1 TO 16 :: FOR X=1 TO 4 :: C(Y,X)=15-C(Y,X):: CALL GETNYBBLE(C(Y,X),N$):: C$(Y,X)=N$ :: NEXT X :: NEXT Y :: CALL DRAWGRID(C$(,),C(,)):: CALL DONE
820 SUBEND
830 ! ##############
840 SUB DOWN(C$(,),C(,))
850 DIM T$(1,4),T(1,4)
860 CALL BUSY :: FOR X=1 TO 4 :: T$(1,X)=C$(16,X):: T(1,X)=C(16,X):: NEXT X
870 FOR Y=16 TO 2 STEP -1 :: FOR X=1 TO 4 :: C$(Y,X)=C$(Y-1,X):: C(Y,X)=C(Y-1,X):: NEXT X :: NEXT Y
880 FOR X=1 TO 4 :: C$(1,X)=T$(1,X):: C(1,X)=T(1,X):: NEXT X :: CALL DRAWGRID(C$(,),C(,)):: CALL DONE
890 SUBEND
900 ! #############
910 SUB UP(C$(,),C(,))
920 DIM T$(1,4),T(1,4)
930 CALL BUSY :: FOR X=1 TO 4 :: T$(1,X)=C$(1,X):: T(1,X)=C(1,X):: NEXT X
940 FOR Y=2 TO 16 :: FOR X=1 TO 4 :: C$(Y-1,X)=C$(Y,X):: C(Y-1,X)=C(Y,X):: NEXT X :: NEXT Y
950 FOR X=1 TO 4 :: C$(16,X)=T$(1,X):: C(16,X)=T(1,X):: NEXT X :: CALL DRAWGRID(C$(,),C(,)):: CALL DONE
960 SUBEND
970 ! ##############
980 SUB LEFT(C$(,),C(,))
990 CALL BUSY
1000 FOR Y=1 TO 16 :: T4=C(Y,4)*2 :: IF T4>15 THEN C4=1 ELSE C4=0
1010 T3=C(Y,3)*2 :: IF T3>15 THEN C3=1 ELSE C3=0
1020 T2=C(Y,2)*2 :: IF T2>15 THEN C2=1 ELSE C2=0
1030 T1=C(Y,1)*2 :: IF T1>15 THEN C1=1 ELSE C1=0
1040 T4=T4 AND 15 :: T3=T3 AND 15 :: T2=T2 AND 15 :: T1=T1 AND 15
1050 C(Y,1)=T1 OR C2 :: C(Y,2)=T2 OR C3 :: C(Y,3)=T3 OR C4 :: C(Y,4)=T4 OR C1
1060 CALL GETNYBBLE(C(Y,1),N$):: C$(Y,1)=N$ :: CALL GETNYBBLE(C(Y,2),N$):: C$(Y,2)=N$ :: CALL GETNYBBLE(C(Y,3),N$):: C$(Y,3)=N$
1070 CALL GETNYBBLE(C(Y,4),N$):: C$(Y,4)=N$ :: NEXT Y
1080 CALL DRAWGRID(C$(,),C(,)):: CALL DONE
1090 SUBEND
1100 ! ############
1110 SUB RIGHT(C$(,),C(,)):: CALL BUSY
1120 FOR Y=1 TO 16 :: C1=(C(Y,1)AND 1)*8 :: C2=(C(Y,2)AND 1)*8 :: C3=(C(Y,3)AND 1)*8 :: C4=(C(Y,4)AND 1)*8
1130 T1=INT(C(Y,1)/2):: T2=INT(C(Y,2)/2):: T3=INT(C(Y,3)/2):: T4=INT(C(Y,4)/2)
1140 C(Y,1)=T1 OR C4 :: C(Y,2)=T2 OR C1 :: C(Y,3)=T3 OR C2 :: C(Y,4)=T4 OR C3
1150 CALL GETNYBBLE(C(Y,1),N$):: C$(Y,1)=N$ :: CALL GETNYBBLE(C(Y,2),N$):: C$(Y,2)=N$ :: CALL GETNYBBLE(C(Y,3),N$):: C$(Y,3)=N$
1160 CALL GETNYBBLE(C(Y,4),N$):: C$(Y,4)=N$ :: NEXT Y
1170 CALL DRAWGRID(C$(,),C(,)):: CALL DONE
1180 SUBEND
1190 ! ##############
1200 SUB HFLIP(C$(,),C(,)):: CALL BUSY
1210 DIM T$(16,4),T(16,4):: FOR Y=1 TO 16 :: XX=4 :: FOR X=1 TO 4
1220 IF C(Y,X)=0 THEN T(Y,XX)=0 ELSE ON C(Y,X)GOSUB 1260,1270,1280,1290,1300,1310,1320,1330,1340,1350,1360,1370,1380,1390,1400
1230 XX=XX-1 :: NEXT X :: NEXT Y
1240 FOR Y=1 TO 16 :: FOR X=1 TO 4 :: C(Y,X)=T(Y,X):: CALL GETNYBBLE(C(Y,X),N$):: C$(Y,X)=N$ :: NEXT X :: NEXT Y :: CALL DRAWGRID(C$(,),C(,))
1250 SUBEXIT
1260 T(Y,XX)=8 :: RETURN
1270 T(Y,XX)=4 :: RETURN
1280 T(Y,XX)=12 :: RETURN
1290 T(Y,XX)=2 :: RETURN
1300 T(Y,XX)=10 :: RETURN
1310 T(Y,XX)=6 :: RETURN
1320 T(Y,XX)=14 :: RETURN
1330 T(Y,XX)=1 :: RETURN
1340 T(Y,XX)=9 :: RETURN
1350 T(Y,XX)=5 :: RETURN
1360 T(Y,XX)=13 :: RETURN
1370 T(Y,XX)=3 :: RETURN
1380 T(Y,XX)=11 :: RETURN
1390 T(Y,XX)=7 :: RETURN
1400 T(Y,XX)=15 :: RETURN
1410 CALL DONE :: SUBEND
1420 ! ##############
1430 SUB VFLIP(C$(,),C(,)):: CALL BUSY :: DIM T(16,4)
1440 YY=16 :: FOR Y=1 TO 16 :: FOR X=1 TO 4 :: T(YY,X)=C(Y,X):: NEXT X :: YY=YY-1 :: NEXT Y
1450 FOR Y=1 TO 16 :: FOR X=1 TO 4 :: CALL GETNYBBLE(T(Y,X),N$):: C$(Y,X)=N$ :: C(Y,X)=T(Y,X):: NEXT X :: NEXT Y :: CALL DRAWGRID(C$(,),C(,))
1460 SUBEND
1470 SUB BUSY :: CALL PATTERN(#1,88):: SUBEND
1480 SUB DONE :: CALL PATTERN(#1,140):: SUBEND
1490 SUB HELP
1500 DISPLAY AT(18,1):"arrows@move 0@plot N@new D@scroll down U@scroll up R@right L@left H@flip l/r V@flip u/d G@load grid"
1510 DISPLAY AT(22,1):"F@file A@assign W@animate 2@small J@put sprite on grid"
1520 SUBEND
1530 ! ##########
1540 SUB SCRATCH(C$(,),C(,)):: CALL HCHAR(18,1,32,6*32)
1550 DISPLAY AT(20,1):"Erase grid? y/n" :: ACCEPT AT(20,16)SIZE(1)VALIDATE("YNyn"):A$
1560 IF A$="y" OR A$="Y" THEN CALL NEW(C$(,),C(,))
1570 CALL HELP
1580 SUBEND
1590 ! ########
1600 SUB ASSIGN(C$(,),C(,))
1610 CALL HCHAR(18,1,32,6*32)
1620 DISPLAY AT(19,1):"Assign design to 1-6:" :: ACCEPT AT(19,22)SIZE(1)VALIDATE("123456"):V$ :: IF V$="" THEN CALL HELP :: SUBEXIT
1630 H$="0123456789ABCDEF" :: CALL BUSY
1640 A$="" :: FOR Y=1 TO 16 :: FOR X=1 TO 2 :: A$=A$&SEG$(H$,C(Y,X)+1,1):: NEXT X :: NEXT Y
1650 FOR Y=1 TO 16 :: FOR X=3 TO 4 :: A$=A$&SEG$(H$,C(Y,X)+1,1):: NEXT X :: NEXT Y
1660 V=116+(VAL(V$)*4)
1670 CALL CHAR(V,A$):: CALL HELP :: CALL DONE
1680 SUBEND
1690 ! ##########
1700 SUB LOADGRID(C$(,),C(,)):: CALL HCHAR(18,1,32,6*32)
1710 H$="0123456789ABCDEF" :: FOR I=1 TO 4
1720 DISPLAY AT(18,1):"Enter data for character";I
1730 ACCEPT AT(19,1)SIZE(16)VALIDATE("0123456789ABCDEF"):A$ :: CALL BUSY
1740 IF I=1 THEN Y=1 ELSE IF I=2 THEN Y=9 ELSE IF I=3 THEN Y=1 ELSE Y=9
1750 IF I<3 THEN S=1 ELSE S=3
1760 FOR M=1 TO LEN(A$)
1770 V=POS(H$,SEG$(A$,M,1),1)-1
1780 C(Y,S)=V :: CALL GETNYBBLE(C(Y,S),N$):: C$(Y,S)=N$ :: IF M/2<>INT(M/2)THEN S=S+1 ELSE S=S-1
1790 IF M/2=INT(M/2)THEN Y=Y+1
1800 NEXT M :: CALL DRAWGRID(C$(,),C(,)):: NEXT I
1810 CALL HELP :: CALL DONE
1820 SUBEND
1830 ! ###########
1840 SUB SAVEDATA
1850 CALL HCHAR(18,1,32,6*32)
1860 DISPLAY AT(18,1):"Enter device and file name, eg DSK1.SPRITES etc Press enter to abort"
1870 ACCEPT AT(21,1)SIZE(15)VALIDATE("_-#@.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"):FN$
1880 IF FN$="" THEN CALL HELP :: SUBEXIT
1890 CALL BUSY :: OPEN #1:FN$,DISPLAY ,VARIABLE 80,OUTPUT
1900 RESTORE 40
1910 FOR I=1 TO 6 :: B$=" DATA " :: READ V :: FOR II=V TO V+3 :: CALL CHARPAT(II,T$):: CALL SPLIT(T$,B$):: NEXT II
1920 PRINT #1:SEG$(B$,1,LEN(B$)-1):: NEXT I
1930 CLOSE #1 :: CALL HELP :: CALL DONE
1940 SUBEND
1950 ! ##############
1960 SUB SPLIT(A$,B$)
1970 FOR I=1 TO 16 STEP 4 :: B$=B$&">"&SEG$(A$,I,4)&"," :: NEXT I
1980 SUBEND
1990 ! ##########
2000 SUB FILEMENU(C$(,),C(,))
2010 CALL HCHAR(18,1,32,6*32):: DISPLAY AT(19,1):"S=SAVE L=LOAD Press enter to abort"
2020 ACCEPT AT(21,1)SIZE(1)VALIDATE("sSlL"):A$
2030 IF A$="" THEN CALL HELP :: SUBEXIT
2040 IF A$="s" OR A$="S" THEN CALL SAVEDATA
2050 IF A$="l" OR A$="L" THEN CALL LOADDATA(C$(,),C(,))
2060 CALL HELP
2070 SUBEND
2080 ! ##########
2090 SUB LOADDATA(C$(,),C(,))
2100 CALL HCHAR(18,1,32,6*32):: DISPLAY AT(20,1):"Enter device and filename Eg DSK1.SPRITES Press Enter to abort"
2110 ACCEPT AT(23,1)SIZE(15)VALIDATE("_-#@.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"):FN$
2120 IF FN$="" THEN CALL HELP :: SUBEXIT
2130 CALL BUSY :: OPEN #1:FN$,INPUT ,DISPLAY ,VARIABLE 80
2140 IF EOF(1)<>0 THEN 2170
2150 RESTORE :: FOR I=1 TO 6 :: LINPUT #1:A$ :: LINPUT #1:B$ :: A$=A$&B$
2160 A$=SEG$(A$,8,LEN(A$)):: B$="" :: XX=1 :: FOR X=1 TO 16 :: B$=B$&SEG$(A$,XX,4):: XX=XX+6 :: NEXT X :: READ V :: CALL CHAR(V,B$):: NEXT I
2170 CLOSE #1
2180 CALL DONE :: SUBEND
2190 ! ############
2200 SUB LFSPRITE(C$(,),C(,))
2210 CALL HCHAR(18,1,32,6*32):: DISPLAY AT(19,1):"Load grid from which sprite?" :: ACCEPT AT(20,1)SIZE(1)VALIDATE("123456"):S$ :: CALL HELP
2220 IF S$="" THEN SUBEXIT ELSE S=VAL(S$)
2230 CALL BUSY :: SP=116+(S*4)
2240 CALL CHARPAT(SP,P1$):: CALL CHARPAT(SP+1,P2$):: CALL CHARPAT(SP+2,P3$):: CALL CHARPAT(SP+3,P4$):: H$="0123456789ABCDEF"
2250 P$=P1$&P2$&P3$&P4$
2260 Y=1 :: FOR I=1 TO 31 STEP 2
2270 V$=SEG$(P$,I,1):: V1=POS(H$,V$,1)-1 :: V$=SEG$(P$,I+1,1):: V2=POS(H$,V$,1)-1
2280 CALL GETNYBBLE(V1,N$):: C$(Y,1)=N$ :: CALL GETNYBBLE(V2,N$):: C$(Y,2)=N$ :: C(Y,1)=V1 :: C(Y,2)=V2 :: Y=Y+1 :: NEXT I
2290 Y=1 :: FOR I=33 TO 63 STEP 2
2300 V$=SEG$(P$,I,1):: V1=POS(H$,V$,1)-1 :: V$=SEG$(P$,I+1,1):: V2=POS(H$,V$,1)-1
2310 CALL GETNYBBLE(V1,N$):: C$(Y,3)=N$ :: CALL GETNYBBLE(V2,N$):: C$(Y,4)=N$ :: C(Y,3)=V1 :: C(Y,4)=V2 :: Y=Y+1 :: NEXT I
2320 CALL DRAWGRID(C$(,),C(,)):: CALL DONE
2330 SUBEND
2340 ! #################
2350 SUB ANIMATE(C$(,),C(,)):: CALL COLOR(#1,1)
2360 FOR I=2 TO 17 :: CALL HCHAR(I,3,32,16):: NEXT I
2370 DISPLAY AT(3,1):"Frame order@" :: DISPLAY AT(4,2):"1234565432"
2380 ACCEPT AT(4,2)VALIDATE(DIGIT)SIZE(-10):o$
2390 CALL DRAWGRID(C$(,),C(,)):: CALL DONE :: SUBEND