Aprendiendo a programar un Emulador (DCPU-16)

Foro dedicado a la programación en todo tipo de sistemas clásicos.
Avatar de Usuario
Hark0
Amiga 1200
Amiga 1200
Mensajes: 1695
Registrado: 11 Jul 2012, 23:44
Sistema Favorito: Spectrum 16Kb/48Kb
primer_sistema: Spectrum 16Kb/48Kb
consola_favorita: (Otro)
Primera consola: (Otro)
Ubicación: Cornellà de Llobregat - Barcelona
Contactar:

Re: Aprendiendo a programar un Emulador (DCPU-16)

Mensajepor Hark0 » 02 Oct 2012, 13:15

Coll*ns!!!!

MUCHO MAS DE LO QUE ESPERABA!!!!!!!!!!!!!!!!!!!!!!!!!!!! =D>

Sigue aburriendote por favor....


Voy a intentar montar tu máquina... ;D


GRACIAS!!!!!!!!!!!!
http://www.zxuno.com
ZX-Uno · Clon de ordenador ZX Spectrum basado en FPGA.

jepalza

Re: Aprendiendo a programar un Emulador (DCPU-16)

Mensajepor jepalza » 02 Oct 2012, 13:34

En casa te puedo mirar el emulador que hice del CHIP8, para que veas un "esqueleto". Está hecho en Qbasic y FreeBasic, y es muy simple y pequeño.
Luego lo busco, y lo cuelgo de mi web, para que lo bajes.

Avatar de Usuario
Hark0
Amiga 1200
Amiga 1200
Mensajes: 1695
Registrado: 11 Jul 2012, 23:44
Sistema Favorito: Spectrum 16Kb/48Kb
primer_sistema: Spectrum 16Kb/48Kb
consola_favorita: (Otro)
Primera consola: (Otro)
Ubicación: Cornellà de Llobregat - Barcelona
Contactar:

Re: Aprendiendo a programar un Emulador (DCPU-16)

Mensajepor Hark0 » 02 Oct 2012, 13:44

Te lo agradeceré... :)

El Qbasic.... que bueno... en su día una tiendecita (sí, esas fantásticas tiendecitas llenas críos arremolinados entre ZX's, MSX's, Amiga's...) me vendió los 4 discos del qb45 por 1200 ptas/disquette...

;)
http://www.zxuno.com
ZX-Uno · Clon de ordenador ZX Spectrum basado en FPGA.

Avatar de Usuario
Hark0
Amiga 1200
Amiga 1200
Mensajes: 1695
Registrado: 11 Jul 2012, 23:44
Sistema Favorito: Spectrum 16Kb/48Kb
primer_sistema: Spectrum 16Kb/48Kb
consola_favorita: (Otro)
Primera consola: (Otro)
Ubicación: Cornellà de Llobregat - Barcelona
Contactar:

Re: Aprendiendo a programar un Emulador (DCPU-16)

Mensajepor Hark0 » 02 Oct 2012, 17:42

Primera duda sobre IRQ:

Cuando dices:

aqui tenemos varias opciones, segun como se maneje la IRQ
- si se ejecuta cada "x" ins seria --> "si numeroins>100saltar a irq" sino numeroins+=1
- si se ejecuta por yiempo fijo o programable --> "si tiempoirq>100 saltar a irq"
(aqui, tiempoirq seria un reloj real, el del PC por ejemplo)
- si la ejecuta una accion externa, por ejemplo, al pulsar una tecla --> "si tecla=1 saltar a irq"
etc. hay muchas variantes
(fin de rutina de estudio de IRQ)



¿Entiendo que las IRQ en el primer caso se "atrapan" cada 100 loops? ¿o es cada 100 numeroins (numero instrucciones) leidas?

¿En el segundo caso será cada X ms de ejecución, esté haciendo la CPU lo que sea?

Tercer caso, he pulsado una tecla,¿ "INYECTO" una IRQ, salgo del mainloop (leer instruccion, decodificar y ejectutarla) y ejecuto la IRQ?


¿quién decide el comportamiento de la IRQ? ¿que opción es la más común?
http://www.zxuno.com
ZX-Uno · Clon de ordenador ZX Spectrum basado en FPGA.

Avatar de Usuario
Joss
Atari 1040 STf
Atari 1040 STf
Mensajes: 930
Registrado: 17 Jul 2012, 20:07
Gracias dadas: 14 veces
Gracias recibidas: 2 veces

Re: Aprendiendo a programar un Emulador (DCPU-16)

Mensajepor Joss » 02 Oct 2012, 19:43

(hoy estais aportando posts muy interesantes ..... :D ... muchas gracias )

Hark0, porque no implementas un emulador de una máquina universal de Turing? No he mirado los detalles, pero debe ser de los computadores mas sencillos que hay:

http://en.wikipedia.org/wiki/Turing_machine

jepalza

Re: Aprendiendo a programar un Emulador (DCPU-16)

Mensajepor jepalza » 02 Oct 2012, 20:02

A ver si soy capaz de explicar lo de las IRQ:

Hay IRQ que se generan solas (la mayoría lo hacen así) y es un reloj interno que va contando milisegundos, y cuando llega a "0", automáticamente se genera dicha IRQ e interrumpe la CPU, dando el control al programa, saltando a la zona indicada en la ROM y ejecutando en ella un código.

Otras IRQ son por instrucciones, por nº de ellas ejecutadas, como cada INS se come un nº de ciclos determinado, el contador interno de la CPU, va sumando los ciclos que se come cada INS, y al llegar a "0", hace como la otra IRQ.

Y luego, la de teclado, o cinta de cassete, o simplemente un reset, activan por hard una patilla concreta de la CPU, y esta, al recibir la orden hace como en los casos anteriores. Este tipo de IRQ se suele llamar "No enmascarable" por que es fija, siempre se ejecuta, es física, y la CPU DEBE hacer caso si o si.

Las otras, se pueden obviar, desactivando la petición de IRQ, pero en ese caso, nunca se genera una, y la CPU nunca para, y queda en un punto muerto, y si el programa no está bien planteado (por programa, me refiero a la bios, al contenido de la ROM) la CPU queda en estado "colgado", nunca vuelve, no se muestra nada en pantalla, etc.

Pero en este caso hablamos de una ROM existente, con código ya probado, y con IRQ ya preparadas. Tú solo debes preocuparte de leer el código de la ROM secuencialmente, según lo pide la CPU (primero la direccion 0, luego la 1, luego la 2... luego, si hay un salto a la 100, saltar a la 100, luego la 101, luego, si hay retorno, volver a la 3, y así siempre).
En el momento en que se genere la IRQ, bien por tiempo, bien por nº de INS ejcutadas, saltas a la rutina de IRQ, pones a cero los contadores, y vuelta a empezar. Los contadores los pones tú a cero, por que la CPU es emulada, no real, y debes emular el reset de contadores. En una CPU real, al saltar a la IRQ, se ponen a cero ellos.

Entonces, volviendo al tema, vas leyendo INS, cada vez que lees una, descuentas el contador (que puede haber empezado en 100, y va bajando hasta el 0), cuando llegas al 0 trás ir descontando y ejecutando INS, mirar que tipo de IRQ es y si es obligatorio o no saltar (ya te digo que hay CPU que permiten anularla y no hacerle caso), y saltas a la rutina.

Todavía no he mirado esa CPU, pero en algún lugar vendrá cómo se tratan sus IRQ. Cada CPU lo hace de un modo, o sea, que debes aprender a usar esas IRQ para programar el emulador. Si las IRQ funcionan por INS ejecutada, debes mirar que ciclos consume cada una (de ahí vienen los famosos RISC o CPU "ultrarápidas", que ejecutan las INS SIEMPRE en el mismo nº de ciclos, generalmente de 4 en 4, con lo que SIEMPRE va a la misma velocidad.) El Z80 es de los mas tragones en cuanto a ciclos, ya que lleva INS de un slo ciclo, pero otras de hasta 8 creo recordar, o sea, que ejecutar una sola INS en el Z80, se come de golpe 8 ciclos, que hacce que la CPU vaya mas lenta con esas INS. Eran en su día INS a evitar en la programación, si querias un código rápido.

No sé que mas explicarte, por ahora.

jepalza

Re: Aprendiendo a programar un Emulador (DCPU-16)

Mensajepor jepalza » 02 Oct 2012, 20:08

Codigo de un emulador de CHIP8 en Qbasic, que hice hace unos años, cuando aún no tenía mucha idea sobre emulación. Lo hice para ir aprendiendo, y me sirvió para entrar en la emulación. Con el tiempo, empecé a programar en "C" y mis primeros emuladores reales (saga Mr.do, LadyBug, City Connection, Speed Ball en Mame) fueron en "C" o "c++"


Código: Seleccionar todo


DEFINT A-Z
DECLARE SUB RUNCPU ()
WIDTH 80, 50

DIM SHARED RAM(4096): REM ­SOLO TIENE UN TOTAL DE 4K DE RAM!

DIM SHARED STACK(16): REM PILA DE MAXIMO DE 16 SALTOS A SUBRUTINAS
DIM SHARED V(16): REM REGISTROS DE VARIABLES DE 0 A F. LA V(F) ES LA DE ESTADOS
DIM SHARED VK(16): REM REGISTROS DE TECLAS DE 0 A F

KR = 0: REM REGISTRO DE TECLA PULSADA (DE 0 A F)
RI = 0: REM REGISTRO INDEXADO "I" DE 12 BITS
PILA = 0: REM DIRECCION DE LA PILA DE RETORNO (MAXIMO 16)
RST = 0: REM RESET SI RST=1
DIR = &H200: REM INICIO DE PROGRAMA SIEMPRE EN LA &H200

CLS
RANDOMIZE TIMER: REM PARA LA SUBRUTINA DE NUMEROS ALEATORIOS


FOR F = 0 TO 15: V(F) = 0: NEXT: REM INICIALIZA LAS VARIABLES

REM CARGA LAS FUENTES EN LA DIRECCION 0 DE LA RAM
D = 0
FOR F = 1 TO 40
READ A
RAM(D) = (A AND &HF0)
D = D + 1
RAM(D) = (A AND &HF) * 16
D = D + 1
NEXT F

FF = 0
AA = 1: BB = 1
DIM FILES$(50): REM max 50 ficheros . ­OJO!
SHELL "dir /B /ON games >files": REM SEGUN VERSION DEL MSDOS DE WIN98
OPEN "files" FOR INPUT AS 1
WHILE NOT EOF(1)
FF = FF + 1
LINE INPUT #1, FILES$(FF)
WEND
CLOSE 1

INICIO:
FOR F = 1 TO 48 - (48 - FF)
LOCATE F, 1: PRINT F; TAB(6); FILES$(F);
   OPEN "GAMES\" + FILES$(F) FOR BINARY ACCESS READ AS 1
   A$ = "  "
   REG& = 1
   WHILE REG& < 100
   GET #1, REG&, A$
   IF A$ = CHR$(0) + CHR$(255) THEN COLOR 6, 0: PRINT TAB(20); "SUPER CHIP8": COLOR 7, 0
   REG& = REG& + 2
   WEND
   CLOSE 1
NEXT

LOCATE AA, 6: COLOR 0, 7: PRINT FILES$(AA); : COLOR 7, 0
1
A$ = UCASE$(INKEY$): IF A$ = "" THEN GOTO 1
IF A$ = CHR$(27) THEN CLS : END
IF LEN(A$) = 2 THEN IF RIGHT$(A$, 1) = "P" THEN AA = AA + 1
IF LEN(A$) = 2 THEN IF RIGHT$(A$, 1) = "H" THEN AA = AA - 1
IF A$ = CHR$(13) THEN N$ = FILES$(AA): CLS : GOTO EMPIEZA
IF AA < 1 THEN AA = FF - 1
IF AA > FF - 1 THEN AA = 1
LOCATE AA, 6: COLOR 0, 7: PRINT FILES$(AA); : COLOR 7, 0
IF BB <> AA THEN LOCATE BB, 6: COLOR 7, 0: PRINT FILES$(BB); : BB = AA
GOTO 1

EMPIEZA:
     
       FOR F = 8 TO 80 - 7: LOCATE 1, F: PRINT CHR$(254): NEXT
       FOR F = 2 TO 33: LOCATE F, 8: PRINT CHR$(254): LOCATE F, 80 - 7: PRINT CHR$(254): NEXT
       FOR F = 8 TO 80 - 7: LOCATE 34, F: PRINT CHR$(254): NEXT

OPEN "GAMES\" + N$ FOR BINARY ACCESS READ AS 1

A$ = " "
REG& = 1: REM PARA SUPERCHIP8, PONER 129 EN VEZ DE 1
D = &H200
WHILE NOT EOF(1)
GET #1, REG&, A$: RAM(D) = ASC(A$)
D = D + 1
REG& = REG& + 1
WEND
CLOSE 1

BUCLE:
  CALL RUNCPU
  IF SALIR = 1 THEN CLS : DIR = &H200: SALIR = 0: GOTO INICIO
GOTO BUCLE


REM DATOS DE LAS FUENTES INTERNAS (SPRITE FONTS) EN LA DIRECCION &H0
REM PARA LOS CARACTERES "0" AL "9", Y "A" A "F" DE 5*8 PIXELS
DATA &Hf9,&H99,&Hf2,&H62,&H27
DATA &Hf1,&Hf8,&Hff,&H1f,&H1f
DATA &H99,&Hf1,&H1f,&H8f,&H1f
DATA &Hf8,&Hf9,&Hff,&H12,&H44
DATA &Hf9,&Hf9,&Hff,&H9f,&H1f
DATA &Hf9,&Hf9,&H9e,&H9e,&H9e
DATA &Hf8,&H88,&Hfe,&H99,&H9e
DATA &Hf8,&Hf8,&Hff,&H8f,&H88

SUB RUNCPU
SHARED RI, DIR, PILA, PAUSA, SALIR

REM RI=REGISTRO INDEXADO

REM CADA VEZ QUE SE EJECUTA UNA INSTRUCCION, DESCUENTA UN 1 A CONTADOR DE PAUSA
PAUSA = PAUSA - 1: : IF PAUSA < 0 THEN PAUSA = 0

REM LEE TECLAS PARA ALMACENAR LA PULSADA EN VK(x)
FOR F = 0 TO 15: VK(F) = 0: NEXT F
INK$ = UCASE$(INKEY$)
    TECLA = 0
    IF (INK$ >= "0" AND INK$ <= "9") = -1 THEN TECLA = VAL(INK$)
    IF (INK$ >= "A" AND INK$ <= "F") = -1 THEN TECLA = VAL("&H" + INK$)
    VK(TECLA) = 1
IF INK$ = CHR$(27) THEN SALIR = 1
OPH = RAM(DIR)
OPL = RAM(DIR + 1)

REM SOLO DEBUG
REM GOTO NODEBUG
LOCATE 35, 1
PRINT "DIRECCION:"; HEX$(DIR); " OPCODE:"; HEX$(OPH); " "; HEX$(OPL); "   "
PRINT "RI="; HEX$(RI); "    "
PRINT "PILA="; PILA; " --> "; HEX$(STACK(PILA)); "   "
FOR F = 0 TO 7: LOCATE 40 + F, 40: PRINT "V"; HEX$(F); ":"; HEX$(V(F)); " "; V(F); "    ": NEXT
FOR F = 8 TO 15: LOCATE 40 + (F - 8), 60: PRINT "V"; HEX$(F); ":"; HEX$(V(F)); " "; V(F); "    ": NEXT
REM A$ = INPUT$(1)
NODEBUG:

DIR = DIR + 2

N0 = (OPH AND &HF0) / 16
N1 = (OPH AND &HF)
N2 = (OPL AND &HF0) / 16
N3 = (OPL AND &HF)

ON N0 + 1 GOTO s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sa, sb, sc, sd, se, sf

s0:
REM codigos 0 **************************************************************
 
   REM 00E0 --> CLS
   IF OPL = &HE0 THEN
       CLS
       FOR F = 8 TO 80 - 7: LOCATE 1, F: PRINT CHR$(254): NEXT
       FOR F = 2 TO 33: LOCATE F, 8: PRINT CHR$(254): LOCATE F, 80 - 7: PRINT CHR$(254): NEXT
       FOR F = 8 TO 80 - 7: LOCATE 34, F: PRINT CHR$(254): NEXT
   END IF
 
   REM 00EE --> RTS: RETORNO DE SUBRUTINA "JSR (1xxx)"
   IF OPL = &HEE THEN
        IF PILA = 0 THEN PRINT "ERROR: PILA DE RETORNO VACIA.": END
        DIR = STACK(PILA)
        PILA = PILA - 1
        IF PILA < 0 THEN PILA = 0
   END IF

   REM 00FF --> HIGH MODE: ACTIVA MODO SUPER CHIP8 (128x64) (EN PRUEBAS)
   IF OPL = &HFF THEN
      CLS : PRINT "JUEGO DE MODO SUPER CHIP8. NO SOPORTADO POR AHORA.": A$ = INPUT$(1): SALIR = 1
   END IF

EXIT SUB

s1:
REM codigos 1 **************************************************************

   REM 1xxx --> JMP: SALTO SIN RETORNO (INCONDICIONAL)
   DIR = N1 * 256 + OPL
   
EXIT SUB

s2:
REM codigos 2 **************************************************************

   REM 2xxx --> JSR: SALTO CON RETORNO (MAXIMO 16 SALTOS)
   PILA = PILA + 1
   STACK(PILA) = DIR
   IF PILA > 16 THEN PRINT "ERROR: DESBORDAMIENTO DE LA PILA": END
   DIR = N1 * 256 + OPL

EXIT SUB

s3:
REM codigos 3 **************************************************************
 
   REM 3rxx --> SKEQ VR,XX: SALTAR SIGUIENTE DIRECCION (2 BYTES) SI VR=XX
   IF V(N1) = OPL THEN DIR = DIR + 2

EXIT SUB

s4:
REM codigos 4 **************************************************************

   REM 4rxx --> SKNE VR,XX: SALTAR SIGUIENTE DIRECCION (2 BYTES) SI VR<>XX
   IF V(N1) <> OPL THEN DIR = DIR + 2

EXIT SUB

s5:
REM codigos 5 **************************************************************

   REM 5ry0 --> SKEQ VR,VY: SALTAR SIGUIENTE DIRECCION (2 BYTES) SI VR=VY
   IF V(N1) = V(N2) THEN DIR = DIR + 2

EXIT SUB

s6:
REM codigos 6 **************************************************************

   REM 6rxx --> MOV VR,XX: CARGA REGISTRO V(R) CON XX
   V(N1) = OPL

EXIT SUB

s7:
REM codigos 7 **************************************************************

    REM 7rxx --> ADD VR,XX: A¥ADE A V(R) EL VALOR XX (SIN ACARREO)
    V(N1) = (V(N1) + OPL) AND &HFF

EXIT SUB

s8:
REM codigos 8 **************************************************************

    REM 8ry0 --> MOV VR,VY: COPIAR VY EN VR
    IF N3 = 0 THEN V(N1) = V(N2): EXIT SUB

    REM 8ry1 --> OR VR,VY: "OREAR" VY EN VR
    IF N3 = 1 THEN V(N1) = V(N1) OR V(N2): EXIT SUB
   
    REM 8ry2 --> AND VR,VY: "ANDEAR" VY EN VR
    IF N3 = 2 THEN V(N1) = V(N1) AND V(N2): EXIT SUB
   
    REM 8ry3 --> XOR VR,VY: "XOREAR" VY EN VR
    IF N3 = 3 THEN V(N1) = V(N1) XOR V(N2): EXIT SUB
   
    REM 8ry4 --> ADD VR,VY: A¥ADIR A VR EL VY (SIN ACARREO)
    IF N3 = 4 THEN
        V(N1) = V(N1) + V(N2)
        IF V(N1) > 255 THEN V(N1) = V(N1) AND &HFF: V(&HF) = 1 ELSE V(&HF) = 0
        EXIT SUB
    END IF

    REM 8ry5 --> SUB VR,VY: RESTA A VR EL VY (VF=1 SI <0)   **(R - Y)**
    IF N3 = 5 THEN
       V(N1) = V(N1) - V(N2)
       IF V(N1) < 0 THEN V(N1) = 256 + V(N1): V(&HF) = 0 ELSE V(&HF) = 1
       EXIT SUB
    END IF

    REM 8r06 --> SHR VR: ROTA A LA DERECHA EL VR. EL BIT 0 SE CARGA EN VF
    IF N3 = 6 THEN V(&HF) = V(N1) AND 1: V(N1) = V(N1) \ 2: EXIT SUB

    REM 8ry7 --> RSB VR,VY: RESTA A VY EL VR (VF=1 SI <0)   **(Y - R)**
    IF N3 = 7 THEN
       V(N1) = V(N2) - V(N1)
       IF V(N1) < 0 THEN V(N1) = 256 + V(N1): V(&HF) = 0 ELSE V(&HF) = 1
       EXIT SUB
    END IF
   
    REM 8r0E --> SHL YR: ROTA A LA IZQUIERDA EL VR. EL BIT 7 SE CARGA EN VF
    IF N3 = &HE THEN V(&HF) = (V(N1) AND &H80) / &H80: V(N1) = (V(N1) * 2) AND &HFF

EXIT SUB

s9:
REM codigos 9 **************************************************************
 
   REM 9ry0 --> SKEQ VR,VY: SALTAR SIGUIENTE DIRECCION (2 BYTES) SI VR<>VY
   IF V(N1) <> V(N2) THEN DIR = DIR + 2

EXIT SUB

sa:
REM codigos a **************************************************************
   
   REM Axxx --> MVI XXX: CARGA EL INDEXADO CON XXX
   RI = N1 * 256 + OPL

EXIT SUB

sb:
REM codigos b **************************************************************

   REM Bxxx --> JMI XXX : SALTA A XXX + V0 (VARIABLE 0)
   STOP: DIR = (N1 * 256 + OPL) + V(0): REM VERIFICAR
   IF DIR > 4096 THEN PRINT "ERROR: DIRECCION DE SALTO FUERA DE 4096": END

EXIT SUB

sc:
REM codigos c **************************************************************

   REM Crxx --> RAND VR,XX: CARGA EN V(R) EL DATO ALEATORIO MENOR O IGUAL A XX
   V(N1) = RND(1) * OPL

EXIT SUB

sd:
REM codigos d **************************************************************
 
   REM Drys --> SPRITE VR,VY,S : DIBUJA GRAFICO EN "VR,VY" DE ALTURA "S"
   
     X = V(N1)
     Y = V(N2)
     S = N3
     V(&HF) = 0: REM BORRA EL ACARREO

   FOR F = 0 TO S - 1
     
     A = RAM(RI + F)
     G = 256
     WHILE G <> 1
       G = G / 2
       IF A AND G THEN A$ = CHR$(219) ELSE A$ = CHR$(32)
       IF Y > 31 THEN Y = 31: A$ = ""
       IF Y < 0 THEN Y = 0: A$ = ""
       IF X > 63 THEN X = 63: A$ = ""
       IF X < 0 THEN X = 0: A$ = ""
       B = SCREEN(Y + 2, X + 9)
     
       IF (A$ = CHR$(219) AND B = 219) = -1 THEN A$ = CHR$(32): V(&HF) = 1: GOTO NOPINTAR
       IF (A$ = CHR$(219) AND B = 32) = -1 THEN A$ = CHR$(219): GOTO NOPINTAR
       IF (A$ = CHR$(32) AND B = 219) = -1 THEN A$ = CHR$(219): GOTO NOPINTAR
       IF (A$ = CHR$(32) AND B = 32) = -1 THEN A$ = CHR$(32):  GOTO NOPINTAR

NOPINTAR:
       LOCATE Y + 2, X + 9: PRINT A$: X = X + 1
     WEND
     Y = Y + 1: X = V(N1)

   NEXT F
   REM    AA$ = INPUT$(1)

EXIT SUB

se:
REM codigos e **************************************************************

   REM Ek9E --> SKPR K: SALTAR SIGUIENTE DIRECCION (2 BYTES) SI VK(KEY)=1
   IF OPL = &H9E THEN IF VK(N1) = 1 THEN DIR = DIR + 2
 
   REM EkA1 --> SKUP K: SALTAR SIGUIENTE DIRECCION (2 BYTES) SI VK(KEY)=0
   IF OPL = &HA1 THEN IF VK(N1) = 0 THEN DIR = DIR + 2

EXIT SUB

sf:
REM codigos f **************************************************************

   REM Fr07 --> GDELAY VR: CARGA LA PAUSA EN VR
   IF OPL = &H7 THEN V(N1) = PAUSA: EXIT SUB
 
   REM Fr0A --> KEY VR: SE PARA EN ESPERA DE UNA TECLA A PULSAR EN VR
   IF OPL = &HA THEN
      WHILE INK$ = ""
        INK$ = UCASE$(INKEY$)
      WEND
      TECLA = 0
      IF (INK$ >= "0" AND INK$ <= "9") = -1 THEN TECLA = VAL(INK$)
      IF (INK$ >= "A" AND INK$ <= "F") = -1 THEN TECLA = VAL("&H" + INK$)
      V(N1) = TECLA
      EXIT SUB
   END IF

   REM Fr15 --> SDELAY VR: ACTIVA LA PAUSA SEGUN VR
   IF OPL = &H15 THEN PAUSA = V(N1): EXIT SUB
 
   REM Fr18 --> SSOUND VR: EMITE UN SONIDO DE DURACION VR (OJO CON LA DURACION)
   IF OPL = &H18 THEN EXIT SUB: REM SOUND 500, V(N1) / 3: EXIT SUB
 
   REM Fr1E --> ADI VR: A¥ADE AL INDEXADO RI EL CONTENIDO DE VR
   IF OPL = &H1E THEN
      RI = RI + V(N1)
      IF RI > 4095 THEN RI = RI - 4096
      EXIT SUB
   END IF
 
   REM Fr29 --> FONT VR: HACE QUE EL INDEXADO RI APUNTE AL CARACTER VR
   IF OPL = &H29 THEN RI = (V(N1) AND &HF) * 5: EXIT SUB

   REM Fr33 --> BCD VR: ALMACENA EN "RI,RI+1,RI+2" EL BCD DEL VR
   IF OPL = &H33 THEN
      A$ = LTRIM$(RTRIM$(STR$(V(N1))))
      IF LEN(A$) = 1 THEN A$ = "00" + A$
      IF LEN(A$) = 2 THEN A$ = "0" + A$
      B = VAL(MID$(A$, 1, 1))
      C = VAL(MID$(A$, 2, 1))
      D = VAL(MID$(A$, 3, 1))
      RAM(RI + 0) = B
      RAM(RI + 1) = C
      RAM(RI + 2) = D
      EXIT SUB
   END IF

   REM Fr55 --> STR V0-VR: ALMACENA (PUSH) LOS REGISTROS DE V(0) A V(R) EN RI
   IF OPL = &H55 THEN
     FOR F = 0 TO N1
       RAM(RI) = V(F)
       RI = RI + 1
     NEXT
     EXIT SUB
   END IF
 
   REM Fr65 --> LDR V0-VR: RECUPERA (POP ) LOS REGISTROS DE V(0) A V(R) DE RI
   IF OPL = &H65 THEN
     FOR F = 0 TO N1
       V(F) = RAM(RI)
       RI = RI + 1
     NEXT
     EXIT SUB
   END IF

END SUB

Avatar de Usuario
Hark0
Amiga 1200
Amiga 1200
Mensajes: 1695
Registrado: 11 Jul 2012, 23:44
Sistema Favorito: Spectrum 16Kb/48Kb
primer_sistema: Spectrum 16Kb/48Kb
consola_favorita: (Otro)
Primera consola: (Otro)
Ubicación: Cornellà de Llobregat - Barcelona
Contactar:

Re: Aprendiendo a programar un Emulador (DCPU-16)

Mensajepor Hark0 » 02 Oct 2012, 20:10

Joss escribió:(hoy estais aportando posts muy interesantes ..... :D ... muchas gracias )

Hark0, porque no implementas un emulador de una máquina universal de Turing? No he mirado los detalles, pero debe ser de los computadores mas sencillos que hay:

http://en.wikipedia.org/wiki/Turing_machine


Acabas de citar uno de mis mejores referentes, Turing. ;)

Actualmente en mis proyectos, ya utilizo en mi código una estructura bastante similar a una máquina de Turing. En el juego que estoy escribiendo para iPad (posiblemente también para todas las plataformas que soporta el compilador), tengo declaradas las variables, el loop principal y las subrutinas como máquinas de estado. Quizás no sea la mejor forma ni la más optimizada para programar un juego, los listados se alargan bastante, aunque son super portables y leibles por casi cualquier persona con nos mínimos conocimientos de programación...pero es la que mejor me va, ya que por motivos profesionales tengo que abandonar el desarrollo en ocasiones por algunos días y al volver me resulta más complicado que programar, digamos, de una forma más "seria".

Hace tiempo, y basandome en las máquinas de estado, desarrollé un juego tipo Game&Watch para PalmOS HD. Se puede descargar de aquí: http://www.handheld.remakes.org/results.php?gamename=

Ahora creo que me toca "subir de nivel", y como apasionado de los 8 bits y tal.... pues eso... el motivo de abrir el hilo...

Saludos ;)
http://www.zxuno.com
ZX-Uno · Clon de ordenador ZX Spectrum basado en FPGA.

Avatar de Usuario
Hark0
Amiga 1200
Amiga 1200
Mensajes: 1695
Registrado: 11 Jul 2012, 23:44
Sistema Favorito: Spectrum 16Kb/48Kb
primer_sistema: Spectrum 16Kb/48Kb
consola_favorita: (Otro)
Primera consola: (Otro)
Ubicación: Cornellà de Llobregat - Barcelona
Contactar:

Re: Aprendiendo a programar un Emulador (DCPU-16)

Mensajepor Hark0 » 02 Oct 2012, 20:11

Ops, estabamos escribiendo a la par... me rebobino 2 post... a estudiar!!!! ;)
http://www.zxuno.com
ZX-Uno · Clon de ordenador ZX Spectrum basado en FPGA.

jepalza

Re: Aprendiendo a programar un Emulador (DCPU-16)

Mensajepor jepalza » 02 Oct 2012, 20:14

El mismo código, pero actualizado al mas moderno FreeBasic

Código: Seleccionar todo


DEFINT A-Z
DECLARE SUB RUNCPU ()
'WIDTH 80, 50
Screen 20

DIM SHARED RAM(4096): ' ­SOLO TIENE UN TOTAL DE 4K DE RAM!
Dim Shared RI, DIRF, PILA, PAUSA, SALIR, VK, TECLA, I$
DIM SHARED STACK(16): ' PILA DE MAXIMO DE 16 SALTOS A SUBRUTINAS
DIM SHARED V(16): ' REGISTROS DE VARIABLES DE 0 A F. LA V(F) ES LA DE ESTADOS
DIM SHARED VT(16): ' REGISTROS DE TECLAS DE 0 A F

TECLA = 255
VK = 255: ' REGISTRO DE TECLA PULSADA (DE 0 A F) (255= NINGUNA)
RI = 0: ' REGISTRO INDEXADO "I" DE 12 BITS
PILA = 0: ' DIRECCION DE LA PILA DE RETORNO (MAXIMO 16)
DIRF = &H200: ' INICIO DE PROGRAMA SIEMPRE EN LA &H200

CLS
RANDOMIZE TIMER: ' PARA LA SUBRUTINA DE NUMEROS ALEATORIOS


FOR F = 0 TO 15: V(F) = 0: NEXT: ' INICIALIZA LAS VARIABLES

' CARGA LAS FUENTES EN LA DIRECCION 0 DE LA RAM
D = 0
FOR F = 1 TO 40
READ A
RAM(D) = (A AND &HF0)
D = D + 1
RAM(D) = (A AND &HF) * 16
D = D + 1
NEXT F

FF = 0
AA = 1: BB = 1
DIM FILES$(50): ' max 50 ficheros . ­OJO!
SHELL "dir /B /ON games >files": ' SEGUN VERSION DEL MSDOS DE WIN98
OPEN "files" FOR INPUT AS 1
WHILE NOT EOF(1)
FF = FF + 1
LINE INPUT #1, FILES$(FF)
WEND
CLOSE 1

INICIO:
FOR F = 1 TO 48 - (48 - FF)
LOCATE F, 1: PRINT F; TAB(6); FILES$(F);
   OPEN "GAMES\" + FILES$(F) FOR BINARY ACCESS READ AS 1
   A$ = "  "
   REG& = 1
   WHILE REG& < 150
   GET #1, REG&, A$
   IF A$ = CHR$(0) + CHR$(255) THEN COLOR 6, 0: PRINT TAB(20); "SUPER CHIP8": COLOR 7, 0
   REG& = REG& + 2
   WEND
   CLOSE 1
NEXT

LOCATE AA, 6: COLOR 0, 7: PRINT FILES$(AA); : COLOR 7, 0
1
A$ = UCASE$(INKEY$): IF A$ = "" THEN GOTO 1
IF A$ = CHR$(27) THEN CLS : END
IF LEN(A$) = 2 THEN IF RIGHT$(A$, 1) = "P" THEN AA = AA + 1
IF LEN(A$) = 2 THEN IF RIGHT$(A$, 1) = "H" THEN AA = AA - 1
IF A$ = CHR$(13) THEN N$ = FILES$(AA): CLS : GOTO EMPIEZA
IF AA < 1 THEN AA = FF - 1
IF AA > FF - 1 THEN AA = 1
LOCATE AA, 6: COLOR 0, 7: PRINT FILES$(AA); : COLOR 7, 0
IF BB <> AA THEN LOCATE BB, 6: COLOR 7, 0: PRINT FILES$(BB); : BB = AA
GOTO 1

EMPIEZA:
     
       FOR F = 8 TO 80 - 7: LOCATE 1, F: PRINT CHR$(254): NEXT
       FOR F = 2 TO 33: LOCATE F, 8: PRINT CHR$(254): LOCATE F, 80 - 7: PRINT CHR$(254): NEXT
       FOR F = 8 TO 80 - 7: LOCATE 34, F: PRINT CHR$(254): NEXT

OPEN "GAMES\" + N$ FOR BINARY ACCESS READ AS 1

A$ = " "
REG& = 1
D = &H200
WHILE NOT EOF(1)
GET #1, REG&, A$: RAM(D) = ASC(A$)
D = D + 1
REG& = REG& + 1
WEND
CLOSE 1

BUCLE:
  CALL RUNCPU
       
        IF TECLA <> 255 THEN GOTO 22
 
  I$ = INKEY$
22
  IF I$ = CHR$(27) THEN SALIR = 1
     
      IF (I$ >= "0" AND I$ <= "9") = -1 THEN VK = VAL(I$)
      IF (I$ >= "A" AND I$ <= "F") = -1 THEN VK = VAL("&H" + I$)
      IF I$ = " " THEN VK = 5
      IF LEN(I$) = 2 THEN
            I$ = RIGHT$(I$, 1)
            IF I$ = "H" THEN VK = 2
            IF I$ = "P" THEN VK = 8
            IF I$ = "K" THEN VK = 4
            IF I$ = "M" THEN VK = 6
      END IF

        IF TECLA <> 255 THEN V(TECLA) = VK: TECLA = 255

  IF SALIR = 1 THEN
      ' RESET
      CLS
      DIRF = &H200
      SALIR = 0
      FOR F = 0 TO 15: V(F) = 0: NEXT: ' INICIALIZA LAS VARIABLES
      PILA = 0
      VK = 255
      RI = 0
      GOTO INICIO
  END IF
GOTO BUCLE


' DATOS DE LAS FUENTES INTERNAS (SPRITE FONTS) EN LA DIRECCION &H0
' PARA LOS CARACTERES "0" AL "9", Y "A" A "F" DE 5*8 PIXELS
DATA &Hf9,&H99,&Hf2,&H62,&H27
DATA &Hf1,&Hf8,&Hff,&H1f,&H1f
DATA &H99,&Hf1,&H1f,&H8f,&H1f
DATA &Hf8,&Hf9,&Hff,&H12,&H44
DATA &Hf9,&Hf9,&Hff,&H9f,&H1f
DATA &Hf9,&Hf9,&H9e,&H9e,&H9e
DATA &Hf8,&H88,&Hfe,&H99,&H9e
DATA &Hf8,&Hf8,&Hff,&H8f,&H88

SUB RUNCPU

' RI=REGISTRO INDEXADO

 FOR F# = 0 TO 1000: NEXT: ' PAUSA DE PRUEBAS

' CADA VEZ QUE SE EJECUTA UNA INSTRUCCION, DESCUENTA UN 1 A CONTADOR DE PAUSA
PAUSA = PAUSA - 1: : IF PAUSA < 0 THEN PAUSA = 0

OPH = RAM(DIRF)
OPL = RAM(DIRF + 1)

' SOLO DEBUG
' GOTO NODEBUG
LOCATE 35, 1
PRINT "DIRECCION:"; HEX$(DIRF); " OPCODE:"; HEX$(OPH); " "; HEX$(OPL); "   "
PRINT "RI="; HEX$(RI); "    "
PRINT "PILA="; PILA; " --> "; HEX$(STACK(PILA)); "   "
FOR F = 0 TO 7: LOCATE 40 + F, 40: PRINT "V"; HEX$(F); ":"; HEX$(V(F)); " "; V(F); "    ": NEXT
FOR F = 8 TO 15: LOCATE 40 + (F - 8), 60: PRINT "V"; HEX$(F); ":"; HEX$(V(F)); " "; V(F); "    ": NEXT
' IF DIRF > &H380 THEN A$ = INPUT$(1)
NODEBUG:

DIRF = DIRF + 2

N0 = (OPH AND &HF0) / 16
N1 = (OPH AND &HF)
N2 = (OPL AND &HF0) / 16
N3 = (OPL AND &HF)

ON N0 + 1 GOTO s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sa, sb, sc, sd, se, sf

s0:
' codigos 0 **************************************************************
 
   ' 00E0 --> CLS
   IF OPL = &HE0 THEN
       CLS
       FOR F = 8 TO 80 - 7: LOCATE 1, F: PRINT CHR$(254): NEXT
       FOR F = 2 TO 33: LOCATE F, 8: PRINT CHR$(254): LOCATE F, 80 - 7: PRINT CHR$(254): NEXT
       FOR F = 8 TO 80 - 7: LOCATE 34, F: PRINT CHR$(254): NEXT
   END IF
 
   ' 00EE --> RTS: RETORNO DE SUBRUTINA "JSR (1xxx)"
   IF OPL = &HEE THEN
        IF PILA = 0 THEN PRINT "ERROR: PILA DE RETORNO VACIA.": END
        DIRF = STACK(PILA)
        PILA = PILA - 1
        IF PILA < 0 THEN PILA = 0
   END IF

   ' 00FF --> HIGH MODE: ACTIVA MODO SUPER CHIP8 (128x64) (EN PRUEBAS)
   IF OPL = &HFF THEN
      CLS : PRINT "JUEGO DE MODO SUPER CHIP8. NO SOPORTADO POR AHORA.": A$ = INPUT$(1): SALIR = 1
   END IF

EXIT SUB

s1:
' codigos 1 **************************************************************

   ' 1xxx --> JMP: SALTO SIN RETORNO (INCONDICIONAL)
   DIRF = N1 * 256 + OPL
   
EXIT SUB

s2:
' codigos 2 **************************************************************

   ' 2xxx --> JSR: SALTO CON RETORNO (MAXIMO 16 SALTOS)
   PILA = PILA + 1
   STACK(PILA) = DIRF
   IF PILA > 16 THEN PRINT "ERROR: DESBORDAMIENTO DE LA PILA": END
   DIRF = N1 * 256 + OPL

EXIT SUB

s3:
' codigos 3 **************************************************************
 
   ' 3rxx --> SKEQ VR,XX: SALTAR SIGUIENTE DIRECCION (2 BYTES) SI VR=XX
   IF V(N1) = OPL THEN DIRF = DIRF + 2

EXIT SUB

s4:
' codigos 4 **************************************************************

   ' 4rxx --> SKNE VR,XX: SALTAR SIGUIENTE DIRECCION (2 BYTES) SI VR<>XX
   IF V(N1) <> OPL THEN DIRF = DIRF + 2

EXIT SUB

s5:
' codigos 5 **************************************************************

   ' 5ry0 --> SKEQ VR,VY: SALTAR SIGUIENTE DIRECCION (2 BYTES) SI VR=VY
   IF V(N1) = V(N2) THEN DIRF = DIRF + 2

EXIT SUB

s6:
' codigos 6 **************************************************************

   ' 6rxx --> MOV VR,XX: CARGA REGISTRO V(R) CON XX
   V(N1) = OPL

EXIT SUB

s7:
' codigos 7 **************************************************************

    ' 7rxx --> ADD VR,XX: A¥ADE A V(R) EL VALOR XX (SIN ACARREO)
    V(N1) = (V(N1) + OPL) AND &HFF

EXIT SUB

s8:
' codigos 8 **************************************************************

    ' 8ry0 --> MOV VR,VY: COPIAR VY EN VR
    IF N3 = 0 THEN V(N1) = V(N2): EXIT SUB

    ' 8ry1 --> OR VR,VY: "OREAR" VY EN VR
    IF N3 = 1 THEN V(N1) = V(N1) OR V(N2): EXIT SUB
   
    ' 8ry2 --> AND VR,VY: "ANDEAR" VY EN VR
    IF N3 = 2 THEN V(N1) = V(N1) AND V(N2): EXIT SUB
   
    ' 8ry3 --> XOR VR,VY: "XOREAR" VY EN VR
    IF N3 = 3 THEN V(N1) = V(N1) XOR V(N2): EXIT SUB
   
    ' 8ry4 --> ADD VR,VY: A¥ADIR A VR EL VY (SIN ACARREO)
    IF N3 = 4 THEN
        V(N1) = V(N1) + V(N2)
        IF V(N1) > 255 THEN V(N1) = V(N1) AND &HFF: V(&HF) = 1 ELSE V(&HF) = 0
        EXIT SUB
    END IF

    ' 8ry5 --> SUB VR,VY: RESTA A VR EL VY (VF=1 SI <0)   **(R - Y)**
    IF N3 = 5 THEN
       V(N1) = V(N1) - V(N2)
       IF V(N1) < 0 THEN V(N1) = 256 + V(N1): V(&HF) = 0 ELSE V(&HF) = 1
       EXIT SUB
    END IF

    ' 8r06 --> SHR VR: ROTA A LA DERECHA EL VR. EL BIT 0 SE CARGA EN VF
    IF N3 = 6 THEN V(&HF) = V(N1) AND 1: V(N1) = V(N1) \ 2: EXIT SUB

    ' 8ry7 --> RSB VR,VY: RESTA A VY EL VR (VF=1 SI <0)   **(Y - R)**
    IF N3 = 7 THEN
       V(N1) = V(N2) - V(N1)
       IF V(N1) < 0 THEN V(N1) = 256 + V(N1): V(&HF) = 1 ELSE V(&HF) = 0
       ' AQUI
       EXIT SUB
    END IF
   
    ' 8r0E --> SHL YR: ROTA A LA IZQUIERDA EL VR. EL BIT 7 SE CARGA EN VF
    IF N3 = &HE THEN V(&HF) = (V(N1) AND &H80) / &H80: V(N1) = (V(N1) * 2) AND &HFF

EXIT SUB

s9:
' codigos 9 **************************************************************
 
   ' 9ry0 --> SKEQ VR,VY: SALTAR SIGUIENTE DIRECCION (2 BYTES) SI VR<>VY
   IF V(N1) <> V(N2) THEN DIRF = DIRF + 2
   
EXIT SUB

sa:
' codigos a **************************************************************
   
   ' Axxx --> MVI XXX: CARGA EL INDEXADO CON XXX
   RI = N1 * 256 + OPL

EXIT SUB

sb:
' codigos b **************************************************************

   ' Bxxx --> JMI XXX : SALTA A XXX + V0 (VARIABLE 0)
   DIRF = (N1 * 256 + OPL) + V(0): ' VERIFICAR
   IF DIRF > 4096 THEN PRINT "ERROR: DIRECCION DE SALTO FUERA DE 4096": END
   
EXIT SUB

sc:
' codigos c **************************************************************

   ' Crxx --> RAND VR,XX: CARGA EN V(R) EL DATO ALEATORIO MENOR O IGUAL A XX
   V(N1) = RND(1) * OPL
   
EXIT SUB

sd:
' codigos d **************************************************************
 
   ' Drys --> SPRITE VR,VY,S : DIBUJA GRAFICO EN "VR,VY" DE ALTURA "S"
   
     X = V(N1)
     Y = V(N2)
     S = N3
     V(&HF) = 0: ' BORRA EL ACARREO

   FOR F = 0 TO S - 1
     
     A = RAM(RI + F)
     G = 256
     WHILE G <> 1
       ER = 0
       G = G / 2
       IF A AND G THEN A$ = CHR$(219) ELSE A$ = CHR$(32)
       IF Y > 31 THEN ER = 1
       IF Y < 0 THEN ER = 1
       IF X > 63 THEN ER = 1
       IF X < 0 THEN ER = 1
       IF ER = 0 THEN B = SCREEN(Y + 2, X + 9)
     
       IF (A$ = CHR$(219) AND B = 219) = -1 THEN A$ = CHR$(32): V(&HF) = 1: GOTO NOPINTAR
       IF (A$ = CHR$(219) AND B = 32) = -1 THEN A$ = CHR$(219): GOTO NOPINTAR
       IF (A$ = CHR$(32) AND B = 219) = -1 THEN A$ = CHR$(219): GOTO NOPINTAR
       IF (A$ = CHR$(32) AND B = 32) = -1 THEN A$ = CHR$(32):  GOTO NOPINTAR

NOPINTAR:
       IF ER = 0 THEN LOCATE Y + 2, X + 9: PRINT A$: X = X + 1
     WEND
     Y = Y + 1: X = V(N1)

   NEXT F
   '    AA$ = INPUT$(1)

EXIT SUB

se:
' codigos e **************************************************************

   ' Ek9E --> SKPR K: SALTAR SIGUIENTE DIRECCION (2 BYTES) SI VT(KEY)=1
   IF OPL = &H9E THEN IF V(N1) = VK THEN DIRF = DIRF + 2: VK = 255
 
   ' EkA1 --> SKUP K: SALTAR SIGUIENTE DIRECCION (2 BYTES) SI VT(KEY)=1
   ' OJO ESTAN AL REVES, ESTUDIARLO CON EL BRIX
   IF OPL = &HA1 THEN IF V(N1) = VK THEN VK = 255 ELSE DIRF = DIRF + 2

EXIT SUB

sf:
' codigos f **************************************************************

   ' Fr07 --> GDELAY VR: CARGA LA PAUSA EN VR
   IF OPL = &H7 THEN V(N1) = PAUSA: EXIT SUB
 
   ' Fr0A --> KEY VR: SE PARA EN ESPERA DE UNA TECLA A PULSAR EN VR
   IF OPL = &HA THEN
      WHILE I$ = "": I$ = UCASE$(INKEY$): WEND
      TECLA = N1: ' OBLIGA A QUE AL SALIR SE META EN V(N1) EL VALOR DE LA TECLA
      EXIT SUB
   END IF

   ' Fr15 --> SDELAY VR: ACTIVA LA PAUSA SEGUN VR
   IF OPL = &H15 THEN PAUSA = V(N1): EXIT SUB
 
   ' Fr18 --> SSOUND VR: EMITE UN SONIDO DE DURACION VR (OJO CON LA DURACION)
   IF OPL = &H18 THEN EXIT SUB: ' SOUND 500, V(N1) / 3: EXIT SUB
 
   ' Fr1E --> ADI VR: A¥ADE AL INDEXADO RI EL CONTENIDO DE VR
   IF OPL = &H1E THEN
      RI = RI + V(N1)
      IF RI > 4095 THEN RI = RI - 4096
      EXIT SUB
   END IF
 
   ' Fr29 --> FONT VR: HACE QUE EL INDEXADO RI APUNTE AL CARACTER VR
   IF OPL = &H29 THEN RI = (V(N1) AND &HF) * 5: EXIT SUB

   ' Fr33 --> BCD VR: ALMACENA EN "RI,RI+1,RI+2" EL BCD DEL VR
   IF OPL = &H33 THEN
      A$ = LTRIM$(RTRIM$(STR$(V(N1))))
      IF LEN(A$) = 1 THEN A$ = "00" + A$
      IF LEN(A$) = 2 THEN A$ = "0" + A$
      B = VAL(MID$(A$, 1, 1))
      C = VAL(MID$(A$, 2, 1))
      D = VAL(MID$(A$, 3, 1))
      RAM(RI + 0) = B
      RAM(RI + 1) = C
      RAM(RI + 2) = D
      EXIT SUB
   END IF

   ' Fr55 --> STR V0-VR: ALMACENA (PUSH) LOS REGISTROS DE V(0) A V(R) EN RI
   IF OPL = &H55 THEN
     FOR F = 0 TO N1
       RAM(RI) = V(F)
       RI = RI + 1
     NEXT
     EXIT SUB
   END IF
 
   ' Fr65 --> LDR V0-VR: RECUPERA (POP ) LOS REGISTROS DE V(0) A V(R) DE RI
   IF OPL = &H65 THEN
     FOR F = 0 TO N1
       V(F) = RAM(RI)
       RI = RI + 1
     NEXT
     EXIT SUB
   END IF

END SUB


Volver a “Programación”

¿Quién está conectado?

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