Snippets en Z80

Programando el Amstrad en Ensamblador.
Reglas del Foro
Debido a que hay varios temas pidiendo ayuda para programar en ensamblador máquinas distintas al Amstrad CPC, con micro distinto al Z80 y que incluso dependen del sistema operativo, nos vemos en la necesidad de poner por escrito que estos posts son bienvenidos pero que no es el lugar adecuado ya que por estos lares nos dedicamos más al ensamblador del Z80, un microprocesador de 8 bits que tuvo su gran auge en ordenadores y consolas de los años 80.

De todas formas, esto no quita que alguien que sepa del asunto pueda postear alguna respuesta pero es más fácil encontrar foros dedicados a programar en ensamblador en Windows o MS-DOS que ayudarán más que nosotros:
http://www.lawebdelprogramador.com/news ... nsamblador
Meta
Forero habitual
Forero habitual
Mensajes: 103
Registrado: Jue 04 Ene , 2018 9:36 am

Macro XYPOS a SCR_ADDR con instrucción pre-formateada

Mensajepor Meta » Dom 17 Ene , 2021 6:29 pm

Macros que nos evita tener que calcular la dirección de pantalla usando coordenadas X,Y añadiéndole una instrucción. Utiliza la fórmula que traen los ejemplos de winape por Richard Wilson y en que también esta en la CPCWiki.

screenaddr = screenbase + (y AND 7)*&800 + int(y/8)*2*R1 + int(x/M)

Pasándo los parámetros reg16, base, X, Y podemos obtener instrucciones "pre-formateadas" para cualquier configuración de pantalla y cualquier registro de 16 bits:
ld de,nnnn
ld hl,(nnnn)
ld (nnnn),sp
dw nnnn
etc...

SCR_WIDTH tiene que estar definido como global (ancho de pantalla en bytes, #50,#40, etc), Al igual que la coordenada XPOS es en bytes.
La razón de que sea global es porque se usa frecuentemente en otras partes de código, ya sea para dibujar sprites, scrolls, etc.

Saludos

Código: Seleccionar todo

;;===================================================
;; LD/LDC/LDR/DW SCREEN ADDR from XYPOS
;; Based on Winape's macro example by Richard Wilson
;; macro params: BC|DE|HL|IX|IY|SP, scr_base_addr, xpos, ypos
;; Globals: SCR_WIDTH (R1*2*MODE)
;; 2-4 bytes
;; Examples:
;; M_LD_SCR_ADDR HL, #C000, 1,3 --> LD HL,#D801
;; M_LDC_SCR_ADDR BC, #8000, 1,3 --> LD BC,(#9801)
;; M_LDR_SCR_ADDR DE, #8000, 1,3 --> LD (#9801),DE
;; M_DW_SCR_ADDR #C000, 1,3 --> DEFW #D801

ifndef SCR_WIDTH:SCR_WIDTH equ #50:endif

macro M_SCR_ADDR base, X, Y
let @y7 = Y and 7 * #800
let addr = Y / 8 * SCR_WIDTH + base + @y7 + X
mend

macro M_LD_SCR_ADDR reg16, base, X, Y
M_SCR_ADDR base, X, Y
ld reg16,addr
mend

macro M_LDC_SCR_ADDR reg16, base, X, Y
M_SCR_ADDR base, X, Y
ld reg16,(addr)
mend

macro M_LDR_SCR_ADDR reg16, base, X, Y
M_SCR_ADDR base, X, Y
ld (addr),reg16
mend

macro M_DW_SCR_ADDR base, X, Y
M_SCR_ADDR base, X, Y
defw addr
mend
Fuentes:
Programming:Calculating xpos,ypos to screen address in assembly - CPCWiki
https://www.cpcwiki.eu/index.php/Progra ... n_assembly
Assembler Macros - WinAPE
http://www.winape.net/help/macros.html

Meta
Forero habitual
Forero habitual
Mensajes: 103
Registrado: Jue 04 Ene , 2018 9:36 am

Rutinas XYPOS/YXPOS a SCR_ADDR

Mensajepor Meta » Vie 22 Ene , 2021 8:03 am

La forma más rápida de hacer un "GET_SCR_ADDR" es creando una tabla de consulta y sumarle XPOS al valor del índice, pero para una pantalla normal de 25 carácteres
tendriamos que usar 400 bytes: (25 filas * 8 lineas * 2 bytes de cada dirección).

Aqui os traigo un par de rutinas que sin hacer demasiadas operaciones la calcula igualmente. La pega es que SCREEN WIDTH debe ser divisible entre 16 para que no sea lenta.

Espero que sea útil, saludos.
-

Rutina normal para base #c000:

Código: Seleccionar todo

SCR_WIDTH EQU #50
;; ==================================================
;; XYPOS to SCR_ADDR by Meta 2021
;; Requires SCR_WIDTH div by #10
;; in: HL (xypos). Out: HL (scr_addr). Destroy: AF,BC
;; 17us + ((SCR_WIDTH/16)*3). 17 bytes + (SCR_WIDTH/16)

ld a,l
and #f8 ;; save yposf8 for later
ld c,a
xor l ;; ypos and 7 * #800
add a ;; add lines (*8)
add a
add a
add #c0
ld l,h ;; xpos -> L
ld h,a ;; lines + baseh --> H
xor a ;; calc rows: (yposf8*2)*(width/16)
rl c
rla
ld b,a
repeat SCR_WIDTH/16
add hl,bc
rend
Macro XYPOS A SCR_ADDR con base configurable.
Detecta si el byte bajo de la dirección base es distinto de 0.

Código: Seleccionar todo

ifndef SCR_WIDTH:SCR_WIDTH EQU #50:endif
;; ==================================================
;; XYPOS to SCR_ADDR by Meta 2021
;; macro params: scr_base_addr
;; Globals: SCR_WIDTH (#50, #40, etc. Requires div by #10)
;; in: HL (xypos). Out: HL (scr_addr). Destroy: AF,BC
;;
;; Example with BaseL = 0:
;; M_XYPOS_SCRADDR #C000
;; Exmaple with BaseL !=0:
;; M_XYPOS_SCRADDR #86c0
;;
;; (baseL = 0):
;; 17us + ((SCR_WIDTH/16)*3). 17 bytes + (SCR_WIDTH/16)
;; (baseL != 0):
;; 21us + ((SCR_WIDTH/16)*3). 19 bytes + (SCR_WIDTH/16)

macro M_XYPOS_SCRADDR base
let @baseh = base/256
let @basel = -@baseh*256+base
ld a,l
and #f8 ;; save yposf8 for later
ld c,a
xor l ;; ypos AND 7 * #800
add a ;; add lines (*8)
add a
add a
ifnot @basel ;; if baseL = 0 then add baseH
add @baseh
endif
ld l,h ;; xpos -> L
ld h,a ;; lines + baseh --> H
xor a ;; calc rows: (yposf8*2)*(width/16)
rl c
rla
ld b,a
repeat SCR_WIDTH/16
add hl,bc
rend
if @basel ;; if baseL != 0 then add baseHL
ld bc,base
add hl,bc
endif
mend
Macro YXPOS A SCR_ADDR.
Igual que la otra pero XPOS ya esta en el registro deseado. (1us más rápida)

Código: Seleccionar todo

ifndef SCR_WIDTH:SCR_WIDTH EQU #50:endif
;; ==================================================
;; YXPOS to SCR_ADDR by Meta 2021
;; macro params: scr_base_addr
;; Globals: SCR_WIDTH (#50, #40, etc. Requires div by #10)
;; in: HL (yxpos). Out: HL (scr_addr). Destroy: AF,BC
;;
;; Example with BaseL = 0:
;; M_YXPOS_SCRADDR #C000
;; Exmaple with BaseL !=0:
;; M_YXPOS_SCRADDR #86c0
;;
;; (baseL = 0):
;; 16us + ((SCR_WIDTH/16)*3). 16 bytes + (SCR_WIDTH/16)
;; (baseL != 0):
;; 20us + ((SCR_WIDTH/16)*3). 18 bytes + (SCR_WIDTH/16)

macro M_YXPOS_SCRADDR base
let @baseh = base/256
let @basel = -@baseh*256+base
ld a,h
and #f8 ;; save yposf8 for later
ld c,a
xor h ;; ypos AND 7 * #800
add a ;; add lines (*8)
add a
add a
ifnot @basel ;; if baseL = 0 then add baseH
add @baseh
endif
ld h,a ;; lines + baseh --> H
xor a ;; calc rows: (yposf8*2)*(width/16)
rl c
rla
ld b,a
repeat SCR_WIDTH/16
add hl,bc
rend
if @basel ;; if baseL != 0 then add baseHL
ld bc,base
add hl,bc
endif
mend

Avatar de Usuario
TFM
Me voy lanzando
Me voy lanzando
Mensajes: 71
Registrado: Lun 29 Jun , 2009 1:02 am
Ubicación: Munich
Contactar:

Re: Snippets en Z80

Mensajepor TFM » Vie 22 Ene , 2021 10:35 pm

Hola amigos,

This is nothing special and it's self explaning, but for newies it could be helpful.


Z80 Tipps & Tricks
------------------

ADD HL,A
--------

Der Befehl ADD HL,A lässt sich folgerndermaßen simulieren:

ADD A,L ;-/ L = L + A
LD L,A ;/
ADC A,H ;--/ H = H + Carry
SUB A,L ; /
LD H,A ;/

Die Ausführungszeit beträgt 5 us

---------------------------------------------------------------------------------

Sinnvolle Nutzung von OUT (n),A bei 16 Bit E/A:
Dabei wird der Wert 'n' an die untere Hälfte des Adressbusses gelegt (A0 bis A7) und A an die obere (A8 bis A15).
Anschließend wir der Wert von Register A an den Datenbus geschickt. Beispiel: Aktiviere die ROMs des SF2:

LD A,&FD / 2 + 3 = 5 us
OUT (&11),A /

Das spaart ein Byte und zwei us gegenüber: LD BC,&FD11:OUT (C),C / 3 + 4 = 7 us

---------------------------------------------------------------------------------

LD A,&02:LD (HL),A --> LD (HL),&02

---------------------------------------------------------------------------------

CALL XXXX:RET --> JP XXXX

---------------------------------------------------------------------------------

LD DE,WERT:XOR A,A:SBC HL,DE --> LD DE,0-WERT:ADD HL,DE

---------------------------------------------------------------------------------

LD HL,&4000:LD (HL),&00 --> LD HL,&4000:LD (HL),L

---------------------------------------------------------------------------------

INC DE --> INC E (innerhalb einer 256 Byte Seite)
--2us- -1us-

-----------------------------------------------------------------------------------------

LD HL,(AAAA):LD DE,&NN00:ADD HL,DE:LD (AAAA),HL --> LD A,(AAAA+1):ADD A,&NN:LD (AAAA+1),A

-----------------------------------------------------------------------------------------

LD H,&00 / Alternativ: ADD A,&58
LD L,A ;HL = LW * 8 / LD L,A
LD DE,TURBO_A / LD H,&B8 - nur 5 us !!!
ADD HL,DE / 9 us

---------------------------------------------------------------------------------

LD A,(XXXX):inc a:LD (XXXX),A --> LD HL,&XXXX:LD A,(HL):inc a:LD (HL),A --> LD HL,&XXXX:INC (HL)
--- 4 us -- --- 4 us --(9) --- 3 us--- -- 2 us - -- 2 us -(8) --- 3 us--- - 3 us -(6)

---------------------------------------------------------------------------------

LD A,(XXXX):INC A:SET 2,A:LD (XXXX),A ---> LD HL,&XXXX:LD A,(HL):INC A:SET 2,A:LD (HL),A ---> LD HL,&XXXX:INC (HL):SET 2,(HL)
--- 4 us -- -1us- - 2us - --- 4 us --(11) --- 3 us--- -- 2 us - -1us- - 2us - -- 2 us -(10) --- 3 us--- - 3 us - --- 4 us--(10)

---------------------------------------------------------------------------------

xor a,a:sub a,l:ld l,a / HL = &0000 - HL (6 us)
sbc a,a:sub a,h:ld h,a /

---------------------------------------------------------------------------------
--
FutureOS update: http://www.FutureOS.de (Update: 2023.10.17)
LambdaSpeak ROM: http://futureos.cpc-live.com/files/Lamb ... by_TFM.zip (Update: 2021.12.26)

Urusergi
Forum Addict
Forum Addict
Mensajes: 381
Registrado: Sab 25 Feb , 2006 5:45 pm

Decremento de un número de 16 bits en memoria

Mensajepor Urusergi » Lun 01 Nov , 2021 6:24 pm

 
La forma habitual para decrementar un número de 16bits desde un puntero a memoria suele ser la siguiente:
 

Código: Seleccionar todo

ld c,(hl)
inc hl
ld b,(hl)
dec bc
ld (hl),b
dec hl
ld (hl),c
ret
17µs y 8 bytes

Pero he encontrado una manera más óptima mientras cotilleaba este hilo del foro de CPCWiki:
 

Código: Seleccionar todo

xor a
cp (hl)
dec (hl)
ret c
inc hl
dec (hl)
dec hl ;opcional
ret
10/18µs y 8 bytes (o 10/16µs y 7 bytes de no ser necesario el último dec hl)

Meta
Forero habitual
Forero habitual
Mensajes: 103
Registrado: Jue 04 Ene , 2018 9:36 am

Optimización escaneo de teclado

Mensajepor Meta » Jue 14 Abr , 2022 8:23 pm

Con permiso de los autores, pongo por aquí una pequeña optimización que hice de la rutina de escaneo de CPCwiki y CPCtelera.

Me dí cuenta que el Port C = -10 (F6h), justamente las veces que tiene que recorrer la matriz de teclado.

ld c,d
lp
...
inc c
jr nz,lp

Por otro lado, el LSB del buffer se alinea también con la matriz, es decir, que el buffer debe estar en xx40h, y la instrucción INI ya se encarga de incrementarlo. Puede que sea algo engorroso, pero bueno.. es una forma de eliminar el registro A.

out (c),l

También se podría omitir la inicialización del PPI control y ponerlo aparte si no se va a manipular, ya que al final de la rutina se restablece.
ld bc,#f782
out (c),c

El contenido de las lineas del buffer para una tecla sin pulsar sería FFh (no CPL).

saludos!

Código: Seleccionar todo

;; in: -
;; destroy: BC,DE,HL

ld hl,#0140 ;; dirección del buffer (por ejemplo)
ld bc,#f782
out (c),c
ld bc,#f40e
ld e,b
out (c),c
ld bc,#f6c0
ld d,b
out (c),c
defb #ed,#71
ld bc,#f792
out (c),c
ld c,d
lp
ld b,d
out (c),l
ld b,e
ini
inc c
jr nz,lp
ld bc,#f782
out (c),c
http://www.cpcwiki.eu/index.php/Program ... d_scanning

Urusergi
Forum Addict
Forum Addict
Mensajes: 381
Registrado: Sab 25 Feb , 2006 5:45 pm

Re: Snippets en Z80

Mensajepor Urusergi » Jue 14 Abr , 2022 10:37 pm

Estoy atónito! :shock: acabas de optimizar la rutina "state of the art" de teclado para el amstrad cpc, y eso que parecía imposible de mejorar. Enhorabuena =D>
Esto vendrá muy bien para cuando retome las conversiones GX4000 y me enfrente de nuevo al dichoso bug del asic (8255).

Se me ocurre que si movemos el buffer a &0040 podemos hacer el out &c0 al puerto &f6xx seguido de un out (c),h y de esta manera la rutina sería compatible con todos los Z80, (por si en un futuro empiezan a surgir clones de cpc en tecnología cmos, como ya pasó con los harlequines spectrum, por ejemplo).

Meta
Forero habitual
Forero habitual
Mensajes: 103
Registrado: Jue 04 Ene , 2018 9:36 am

Re: Snippets en Z80

Mensajepor Meta » Vie 15 Abr , 2022 12:05 am

Gracias Urusergi, espero que sea útil a la comunidad :)

Creo que se queda en 194us y si le quitas el control en 187us

Mover el buffer es buena idea, todo lo que sea mejorar lo presente bienvenido sea!,
pero a mi personalmente en amstrad me gusta usar las instrucciones RST y quizá es un sitio un poco así así... Que cada programador busque el emplazamiento que más le convenga.

Mira a ver si le puedes dar un par de vueltas.

Meta
Forero habitual
Forero habitual
Mensajes: 103
Registrado: Jue 04 Ene , 2018 9:36 am

Re: Snippets en Z80

Mensajepor Meta » Vie 15 Abr , 2022 12:07 am

De todos modos el mérito no es mio...cuando ví la rutina por primera vez me quede pasmao. :shock:

Meta
Forero habitual
Forero habitual
Mensajes: 103
Registrado: Jue 04 Ene , 2018 9:36 am

Re: Snippets en Z80

Mensajepor Meta » Vie 15 Abr , 2022 12:31 am

...para quien tenga prisa, CPU a tope! (154us - 92 bytes)

Código: Seleccionar todo

;; unrolled
;; in: -
;; destroy: BC,DE,HL

ld hl,#0140

ld bc,#f782
out (c),c
ld bc,#f40e
ld e,b
out (c),c
ld bc,#f6c0
ld d,b
out (c),c
defb #ed,#71
ld bc,#f792
out (c),c

repeat 10
ld b,d
out (c),l
ld b,e
ini
rend

ld bc,#f782
out (c),c
otra sin el registro DE

Código: Seleccionar todo

;; in: -
;; destroy: A,BC,HL

ld hl,#0140

ld bc,#f782
out (c),c

ld bc,#f40e
out (c),c
ld bc,#f6c0
ld a,b
out (c),c
defb #ed,#71
ld bc,#f792
out (c),c

ld c,#f4
repeat 10
ld b,a
out (c),l
ld b,c
ini
rend

ld bc,#f782
out (c),c

Urusergi
Forum Addict
Forum Addict
Mensajes: 381
Registrado: Sab 25 Feb , 2006 5:45 pm

Re: Snippets en Z80

Mensajepor Urusergi » Vie 15 Abr , 2022 2:14 pm

Mover el buffer es buena idea, todo lo que sea mejorar lo presente bienvenido sea!,
pero a mi personalmente en amstrad me gusta usar las instrucciones RST y quizá es un sitio un poco así así... Que cada programador busque el emplazamiento que más le convenga.

Mira a ver si le puedes dar un par de vueltas.
Ahora ya si, no veo que se pueda mejorar más la rutina [-(
En cuanto a los RST se puede hacer como ya hace el firmware, un JP a otra dirección de memoria, o bien limitar la rutina del RST 38 a un máximo de 8 bytes 8-[

Meta
Forero habitual
Forero habitual
Mensajes: 103
Registrado: Jue 04 Ene , 2018 9:36 am

Re: Snippets en Z80

Mensajepor Meta » Vie 15 Abr , 2022 6:01 pm

Si, quise decir que están las interrupciones ahí al lado y el manejador que tengo ahora me ocupa hasta #47.

Es una gran idea ponerlo el buffer #0040, es un sitio perfecto por que se queda todo agrupado.

Dejo por aquí la rutina al detalle con la mejora de Urusergi:

Código: Seleccionar todo

;; Optimized CPCwiki's keyboard scan routine
;; More info:
;; http://www.cpcwiki.eu/index.php/Programming:Keyboard_scanning
;;
;; In: -
;; Destroy: BC,DE,HL

ld hl,#40 ;; Buffer address

ld bc,#f782 ;; PPI control / Ports A,C: output
out (c),c ;; write to control register

ld bc,#f40e ;; PPI port A / PSG index: Keyboard
ld e,b ;; F4h for later
out (c),c ;; select register
ld bc,#f6c0 ;; PPI port C / PSG operation
ld d,b ;; F6h for later
out (c),c ;; select register
out (c),h ;; PSG inactive (H=0)
ld bc,#f792 ;; PPI control / Ports A,C: input, output
out (c),c ;; write to control register

ld c,d ;; set counter (-10)
lp
ld b,d ;; port C
out (c),l ;; Select line 40 to 49
ld b,e ;; port A
ini ;; Read matrix, write and increment buffer
inc c ;; increment counter -10 to 0
jr nz,lp ;; loop while counter

ld bc,#f782 ;; PPI control / Ports A,C: output
out (c),c ;; write to control register

Urusergi
Forum Addict
Forum Addict
Mensajes: 381
Registrado: Sab 25 Feb , 2006 5:45 pm

Re: Snippets en Z80

Mensajepor Urusergi » Mié 21 Sep , 2022 4:38 pm

Una nimiedad, mientras leía código máquina de un juego comercial...

Para imprimir caracteres en pantalla usa esta rutina (adaptada para simplificar):
 

Código: Seleccionar todo

ORG #6000

ld hl,hello
loop: ld a,(hl)
inc hl
bit 7,a
jr nz,exit
call #bb5a
jr loop

exit: and #7f
jp #bb5a

hello db "Hello, World","!"+&80

Y se me ocurrió algo que no sé si ya está inventado, pero por si acaso lo dejo caer por aquí:
 

Código: Seleccionar todo

ORG &6000

ld hl,hello
loop: ld a,(hl)
inc hl ; puede ser inc l
rlca ; stuvwxyz -> tuvwxyzs & carry=s
srl a ; tuvwxyzs -> 0tuvwxyz & carry=s
call #bb5a ; conserva flags
jr nc,loop
ret

hello db "Hello, World","!"+&80
Creo que está bastante claro, no hace falta añadir ninguna explicación más.

 

Meta
Forero habitual
Forero habitual
Mensajes: 103
Registrado: Jue 04 Ene , 2018 9:36 am

Re: Snippets en Z80

Mensajepor Meta » Jue 22 Sep , 2022 9:21 am

Yo uso estas dos con diferencias en la terminación de las cadenas.
A la segunda no le dí muchas vueltas la verdad. Saludos

Código: Seleccionar todo

;; STRING OUT (FIRMWARE)
;; In HL (str_addr). Destroy AF,HL

.txt_string_fw:
ld a,(hl) ;; get char
rlca ;; str end?
ret c ;; exit if carry
rrca ;; restore char
call TXT_OUTPUT ;; show char
inc hl ;; next char
jr txt_string_fw ;; loop

;; STRING OUT (FIRMWARE)
;; In HL (str_addr). Destroy AF,HL

.txt_string_fw:
ld a,(hl) ;; get char
res 7,a
call TXT_OUTPUT ;; show char
bit 7,(hl)
inc hl ;; next char
jr z,txt_string_fw
ret

Urusergi
Forum Addict
Forum Addict
Mensajes: 381
Registrado: Sab 25 Feb , 2006 5:45 pm

Re: Snippets en Z80

Mensajepor Urusergi » Sab 24 Sep , 2022 6:42 pm

Tu segunda rutina usa la misma terminación de cadena que la mía, así que le puedes ganar un byte si decides sustituirla :-ss

Y viendo tu primera rutina me ha hecho pensar en otra terminación de cadena que sería de lo más óptima y además tiene la 'feature' de encriptar los mensajes, que a veces puede resultar de lo más conveniente :idea:

La terminación es realmente la misma que la que usa mi rutina y la segunda tuya, pero cambiando la posición del bit terminador, del más significativo pasa a ser el menos significativo:

Código: Seleccionar todo

ORG #6000

ld hl,hello
and a ; borra el flag del carry (puede no ser necesario)
loop: ld a,(hl)
inc hl ; podria ser inc l
rra
call #bb5a
jr nc,loop
ret
hello db "H"*2,"e"*2,"l"*2,"l"*2,"o"*2,","*2," "*2,"W"*2,"o"*2,"r"*2,"l"*2,"d"*2,"!"*2+1
Saludos

Meta
Forero habitual
Forero habitual
Mensajes: 103
Registrado: Jue 04 Ene , 2018 9:36 am

Re: Snippets en Z80

Mensajepor Meta » Dom 25 Sep , 2022 11:27 am

Sí sí, ya lo ví, la usaré gracias :)

Encriptar pude ser muy útil para que los jugadores no hagan trampas, esta guay.

Y por ampliar un poco, en winape hay una par de directivas tambien muy útiles, STR y CHARSET.
Supongo que las conoces:

STR ya te pone el bit 7 en el último carácter y el código queda un poco más limpio.

STR "Hello world!"

y CHARSET para sustituir el valor de carácter, creo que era algo así:

CHARSET "A","B" ;; cambiar el valor de A por B
o
CHARSET "A","Z","0" ;; de A a Z trasladar a 0..


Con los códigos de control tambien hay algunos trucos muy majos:

saltar de párrafo, cambiar color, o meter un BEEP en las cadenas entre otros

https://www.cpcwiki.eu/imgs/1/19/S968ap07.pdf


¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro


La Comunidad Española
ESP Soft, juegos para tu CPC Foro de Amstrad CPC Todos los juegos para CPC en un CD Web dedicada al Amstrad CPC (utilidades) Información útil para el CPC (talleres) Selección de juegos de Amstrad CPC Mundo CPC Pree Play then any Key CPC Basic