Juego de Minas

Computación. Programación de ordenadores. Software. Programas. Configuración. Celdas. Interacción

  • Enviado por: Jorge
  • Idioma: castellano
  • País: México México
  • 16 páginas
publicidad
publicidad

Juego de Minas

Hay que recordar el objetivo del juego, el cual es explorar el campo de minas sin tocar ni una. Este campo es un arreglo de 2 dimesiones (matriz) donde algunas celdas tienen las minas escondidas y otras estan vacias. Al principio del juego, todas las celdas estan cerradas y el jugador debe descubrir una por una, de esta manera el jugador gana al descubrir todas las celdas vacias.

Por turno, el jugador puede abrir una celda op poner una banderita si se esta seguro que hay una mina. Si abre una celda que tenga una mina, esyta explota y pierde. Al abrir una celda que no tengo mina y muestra un numero, este numero es la cantiad de minas que hay a su alrededor.

Juego de Minas

Separamos la implementacion del juego en 3 partes.

  • El juego abstracto, incluyendo la representación interna del campo de minas como las funciones que manipulan su representación.

  • Juego grafico. Aquí se incluyen las funciones para mostrar las celdas.

  • La interaccion programa - jugador.

  • El abstracto campo de minas.

    El campo de minas esta definido por sus dimensiones y el numero de minas que contiene. Agrupamos estos 3 datos en un registro y se define la configuración normal de 10x10 celdas y 15 minas.


    # type config = {
    nbcols : int ;
    nbrows : int ;
    nbmines : int };;
    # let default_config = { nbcols=10; nbrows=10; nbmines=15 } ;;

    El campo de minas.

    Es natural representar el campo de minas como un arreglo de 2 dimensiones. Sin embargo es necesario especificar las celdas que son, y la información que da. El estado de una celda se da respondiendo estas preguntas:

    • Hay una mina en esta celda?

    • La celda ya esta abierta?

    • Tiene banderita?

    • Cuantas minas alrededor tiene?

    El ultimo articulo no es obligatorio, pues es posible computarlo cuando sea necesario. Sin embargo, es mas simple hacerlo al principio del juego.

    Con el registro contenido por las 4 datos la celda se representa de esta manera.

    # type cell = {
    mutable mined : bool ;
    mutable seen : bool ;
    mutable flag : bool ;
    mutable nbm : int
    } ;;

    El arreglo bidimenisonal es un arreglo de arreglo de celdas.


    # type board = cell array array ;;

    El iterador.

    En el resto del programa, se necesita iterar una function sobre todas las celdas del campo de minas. Para hacerlo, se define el operador iter_cells que aplica una funcion (f), dada como un argumento, para cada celda en el arreglo, esta definida con la configuración (cf)


    # let iter_cells cf f =
    for i=0 to cf.nbcols-1 do for j=0 to cf.nbrows-1 do f (i,j) done done ;;
    val iter_cells : config -> (int * int -> 'a) -> unit = <fun>

    este es un muy buen ejemplo de la mezcla entre la programacion funcional e imperativa, asi como usamos elevadas funciones de orden, para iterar una funcion que opera a traves de efectos secundarios.

    Inzializacion.

    Arbitrariamente se escoje cuales celdas con minas. Si (c) y (r) representan el numero de columnas y filas del arreglo de minas, y (m) es el numero de minas, se necesita generar (m) con difeentes numeros entre 1 y c X r.

    Supongase que m ð c× r para definer el algoritmo, pero el programa al usarlo necesitara checar esta condicion.

    El algoritmo directo consiste en comenzar con una lista vacia, escogiendo un numero al azar y poniendolo en la lista si no esta, realizando esto hasta que la lista contenga los numeros de (m). utilizamos las funciones siguientes de los modulos del sistema al azar:

    • Random.int: int-> int, escoje un numero entre el 0 y n - 1(n es el argumento) según el generador de un numero al azar.

    • Random.init:int-> unit, inicializa el generador de números al azar.

    • Sys.time:unit-> flota, regresa el tiempo en milisegundos de proceso que el programa utilizo desde que se inicializo. Esta funcion se utilizara para inicializar el generador de numeros al azar para diferente juego.

    La funcion de posición al azar de minas recibe el numero de celdas y el numero de minas y muestra una lista de la posición linear de estas minas.

    # let random_list_mines cr m =
    let cell_list = ref []
    in while (List.length !cell_list) < m do
    let n = Random.int cr in
    if not (List.mem n !cell_list) then cell_list := n :: !cell_list
    done ;
    !cell_list ;;
    val random_list_mines : int -> int -> int list = <fun>

    En la practica, un programa determinado regularmente toma el mismo tiempo de ejecucion, lo que resulta en un resultado similar para generate_seed para cada corrida.

    Regularmente se necesita conocer el vecina de una celda dada durante la inizalizacion del campo de la mina asi como durante el juego. Por lo tanto se escribe una funcion neighbors. Esta funcion debe llevar en accion a las celdas de a lado y esquina que tienen unos cuantos vecinos asi como las de enmedio (funtion valid).


    # let valid cf (i,j) = i>=0 && i<cf.nbcols && j>=0 && j<cf.nbrows ;;
    val valid : config -> int * int -> bool = <fun>
    # let neighbors cf (x,y) =
    let ngb = [x-1,y-1; x-1,y; x-1,y+1; x,y-1; x,y+1; x+1,y-1; x+1,y; x+1,y+1]
    in List.filter (valid cf) ngb ;;
    val neighbors : config -> int * int -> (int * int) list = <fun>

    La funcion initialize_board crea la el campo de l amina inicial. Procediendo en 4 pasos:

  • Generacion de lista de celdas minadas;

  • Creacion de dos arrays diferentes conteniendo celdas diferentes

  • Colocacion de celdas minadas en el tablero

  • Computacion de el numero de minas en las celdas vecinas para cada celda que no esta minada.

  • La funcion initialize_board utiliza unas funciones locales que se descrien brevemente.

    cell_init

    : crea un valor inicial en la celda;

    copy_cell_init

    : coloca una copia del valor inicial de la celda del tablero;

    set_mined

    : coloca una mina en una celda;

    count_mined_adj

    : computa el numero de minas en una celda vecina dada;

    set_count

    : actualiza el numero de minas en el vecino de una celda que no esta minada.


    # let initialize_board cf =
    let cell_init () = { mined=false; seen=false; flag=false; nbm=0 } in
    let copy_cell_init b (i,j) = b.(i).(j) <- cell_init() in
    let set_mined b n = b.(n / cf.nbrows).(n mod cf.nbrows).mined <- true
    in
    let count_mined_adj b (i,j) =
    let x = ref 0 in
    let inc_if_mined (i,j) = if b.(i).(j).mined then incr x
    in List.iter inc_if_mined (neighbors cf (i,j)) ;
    !x
    in
    let set_count b (i,j) =
    if not b.(i).(j).mined
    then b.(i).(j).nbm <- count_mined_adj b (i,j)
    in
    let list_mined = random_list_mines (cf.nbcols*cf.nbrows) cf.nbmines in
    let board = Array.make_matrix cf.nbcols cf.nbrows (cell_init ())
    in iter_cells cf (copy_cell_init board) ;
    List.iter (set_mined board) list_mined ;
    iter_cells cf (set_count board) ;
    board ;;
    val initialize_board : config -> cell array array = <fun>

    Abriendo una celda

    Durante un juego, el jugador abre una celda en donde los vecinos estan (ninguna contiene mina), el sabe que puede abrir una celda vecina sin ningun riesgo, y puede seguir abriendo celdas mientras el abre celdas sin una celda con minas. En orden para tranquilizar al jugador de este proceso. (ya que no es un reto), nuestro buscador de minas abre estas celdas por si solo, Hasta este final se escribe la funcion cells_to_see que regresa una lista de todas estas celdas para abrirse cuando una celda dada es abierta.\

    El algoritmo que se necesita es simple: si la celda abierta tiene algunos vecinos que contienen una mina, entonces la lista de celdas para observar consiste unicamente de una celda abierta; asi como la lista de celdas a ver de estas celdas vecinas. La dificultad es en escribir un programa que no hace ciclo, cada celda es un vecino de cualquiera de sus vecinos. Se debe evitar procesar la misma celda dos veces.

    Para recordar que celdas han sido procesadas se utiliza un array de Booleans visited su tama?o es el mismo que el campo minado. El valor true para una celda es su array denota que ya ha sido visitado. Se recurre solamente a celdas que no han sido visitadas.

    Usamos la funcion auxiliary relevant que computa dos sublistas de la lista de vecinos en una celda. Cada una de estas listas solocontienen celdas que no contienen una mina que no han sido abiertas, que no estan marcadas por el jugador, y que no han sido visitada. La primer sublista es la lista de celdas vecinas que al menos tienen una celda vecina que tiene una mina; la Segunda sublista es la lista de celdas vecinas vacias.

    Mientras estas listas son computadas, todas estas celdas son marcada como visitadas. Notese que celdas marcadas no son procesadas, el proposito de una bandera es prevenir

    abrir una celda.

    The local function cells_to_see_rec implements the recursive search loop. It…….

    TRADUCCION

    • Pag. 07

    Juego de Minas

    Ventana Principal de Buscaminas

    Los siguientes parametros caracterizan a los componentes de la ventana grafica.


    # let b0 = 3 ;;
    # let w1 = 15 ;;
    # let w2 = w1 ;;
    # let w4 = 20 + 2*b0 ;;
    # let w3 = w4*default_config.nbcols + 2*b0 ;;
    # let w5 = 40 + 2*b0 ;;

     


    # let h1 = w1 ;;
    # let h2 = 30 ;;
    # let h3 = w5+20 + 2*b0 ;;
    # let h4 = h2 ;;
    # let h5 = 20 + 2*b0 ;;
    # let h6 = w5 + 2*b0 ;;

     

    Los usamos para extender la configuracion basica de nuestro tablero de “buscaminas” (valor del tipo config). Abajo, definimos un registro tipo window_config. El campo `cf' contiene la configuracion basica. Asociamos una caja con todos los componentes de la exhibicion: ventana principal (campo main_box), campo de mina (campo field_box), dialogo de ventana (campo dialog_box) con dos subcuadros (campo d1_box y d2_box), boton flagging (campo flag_box) y una celda recurrente (campo current_box).


    # type window_config = {
    cf : config ;
    main_box : box_config ;
    field_box : box_config ;
    dialog_box : box_config ;
    d1_box : box_config ;
    d2_box : box_config ;
    flag_box : box_config ;
    mutable current_box : box_config ;
    cell : int*int -> (int*int) ;
    coor : int*int -> (int*int)
    } ;;

    • Pag. 08

    Mas a cerca de un registro de tipo window_config contiene 2 funciones:

    • Celda: toma las coordenadas de una celda y regresa las coordenadas del cuadro correspondiente;

    • Coor: toma las coordenadas de un pixel de la ventana y regresa las coordenadas de la celda correspondiente.

    Configuracion

    Ya sabemos como definir una funcion que contruya una configuracion grafica (de tipo window_config) acorde con la configuracion basica (de tipo config) y los parametros de arriba. El valor de los parametros de algunos coponentes depende del valor de los parametros de otro componentes. Por ejemplo, el ancho del cuadro global depende del ancho del campo de minas, el cual, a su vez depende del numero de columnas. Para evadir el poner el mismo valor varias veces, incrementamos la creacion de componentes. Esta fase inicial de una configuracion grafica es siempre un poco tediosa cuando no hay un inicio adecuado o herramientas disponibles.


    # let make_box x y w h bw r =
    { x=x; y=y; w=w; h=h; bw=bw; r=r; b1_col=gray1; b2_col=gray3; b_col=gray2 } ;;
    val make_box : int -> int -> int -> int -> int -> relief -> box_config =
    <fun>
    # let make_wcf cf =
    let wcols = b0 + cf.nbcols*w4 + b0
    and hrows = b0 + cf.nbrows*h5 + b0 in
    let main_box = let gw = (b0 + w1 + wcols + w2 + b0)
    and gh = (b0 + h1 + hrows + h2 + h3 + h4 + b0)
    in make_box 0 0 gw gh b0 Top
    and field_box = make_box w1 h1 wcols hrows b0 Bot in
    let dialog_box = make_box ((main_box.w - w3) / 2)
    (b0+h1+hrows+h2)
    w3 h3 b0 Bot
    in
    let d1_box = make_box (dialog_box.x + b0) (b0 + h1 + hrows + h2)
    ((w3-w5)/2-(2*b0)) (h3-(2*b0)) 5 Flat in
    let flag_box = make_box (d1_box.x + d1_box.w)
    (d1_box.y + (h3-h6) / 2) w5 h6 b0 Top in
    let d2_box = make_box (flag_box.x + flag_box.w)
    d1_box.y d1_box.w d1_box.h 5 Flat in
    let current_box = make_box 0 0 w4 h5 b0 Top
    in { cf = cf;
    main_box = main_box; field_box=field_box; dialog_box=dialog_box;
    d1_box=d1_box;
    flag_box=flag_box; d2_box=d2_box; current_box = current_box;
    cell = (fun (i,j) -> ( w1+b0+w4*i , h1+b0+h5*j)) ;
    coor = (fun (x,y) -> ( (x-w1)/w4 , (y-h1)/h5 )) } ;;
    val make_wcf : config -> window_config = <fun>

    • Pag. 09

    Despliegues de celdas

    Ahora necesitamos escribir las funciones de despliegue de las celdas en diferentes estados. Una celda puede abrirse o cerrarse y puede contener alguna informacion. Nosotros siempre desplegamos (el cuadro correspondiente con) la celda recurrente en la configuracion del juego (campo field cc_bcf).

    Des este modo escribimos dos funciones modificando la configuracion de la celda recurrente; una cerrandola y otra abriendola.


    # let close_ccell wcf i j =
    let x,y = wcf.cell (i,j)
    in wcf.current_box <- {wcf.current_box with x=x; y=y; r=Top} ;;
    val close_ccell : window_config -> int -> int -> unit = <fun>
    # let open_ccell wcf i j =
    let x,y = wcf.cell (i,j)
    in wcf.current_box <- {wcf.current_box with x=x; y=y; r=Flat} ;;
    val open_ccell : window_config -> int -> int -> unit = <fun>

    Dependiendo de la fase del juego, puede que necesitemos desplegar alguna informacion en las celdas. Para cada caso escribimos una funcion especializada.

    Muestra de una celda cerrada.


    • # let draw_closed_cc wcf i j =
      close_ccell wcf i j;
      draw_box wcf.current_box ;;
      val draw_closed_cc : window_config -> int -> int -> unit = <fun>

    Muestra de una celda abierta con su numero de minas vecinas.


    • # let draw_num_cc wcf i j n =
      open_ccell wcf i j ;
      draw_box wcf.current_box ;
      if n<>0 then draw_string_in_box Center (string_of_int n)
      wcf.current_box Graphics.white ;;
      val draw_num_cc : window_config -> int -> int -> int -> unit = <fun>

    • Pag. 10

    Muestra de una celda que contiene una mina


    • # let draw_mine_cc wcf i j =
      open_ccell wcf i j ;
      let cc = wcf.current_box
      in draw_box wcf.current_box ;
      Graphics.set_color Graphics.black ;
      Graphics.fill_circle (cc.x+cc.w/2) (cc.y+cc.h/2) (cc.h/3) ;;
      val draw_mine_cc : window_config -> int -> int -> unit = <fun>

    Muestra de una celda marcada que contiene una mina.


    • # let draw_flag_cc wcf i j =
      close_ccell wcf i j ;
      draw_box wcf.current_box ;
      draw_string_in_box Center "!" wcf.current_box Graphics.blue ;;
      val draw_flag_cc : window_config -> int -> int -> unit = <fun>

    Muestra de la celda mal marcada.


    • # let draw_cross_cc wcf i j =
      let x,y = wcf.cell (i,j)
      and w,h = wcf.current_box.w, wcf.current_box.h in
      let a=x+w/4 and b=x+3*w/4
      and c=y+h/4 and d=y+3*h/4
      in Graphics.set_color Graphics.red ;
      Graphics.set_line_width 3 ;
      Graphics.moveto a d ; Graphics.lineto b c ;
      Graphics.moveto a c ; Graphics.lineto b d ;
      Graphics.set_line_width 1 ;;
      val draw_cross_cc : window_config -> int -> int -> unit = <fun>

    Durante el juego, la opcion de usar una funcion para desplegar es hecha por:


    # let draw_cell wcf bd i j =
    let cell = bd.(i).(j)
    in match (cell.flag, cell.seen , cell.mined ) with
    (true,_,_) -> draw_flag_cc wcf i j
    | (_,false,_) -> draw_closed_cc wcf i j
    | (_,_,true) -> draw_mine_cc wcf i j
    | _ -> draw_num_cc wcf i j cell.nbm ;;
    val draw_cell : window_config -> cell array array -> int -> int -> unit =
    <fun>

    • Pag. 11

    Una funcion especializada desplega todas las celdas al final del juego. Esta es un poco diferente de la pasada ya que todas las celdas se toman como abiertas. Despues, un cruz roja indica las celda vacias donde el jugador coloco mal las banderas.


    # let draw_cell_end wcf bd i j =
    let cell = bd.(i).(j)
    in match (cell.flag, cell.mined ) with
    (true,true) -> draw_flag_cc wcf i j
    | (true,false) -> draw_num_cc wcf i j cell.nbm; draw_cross_cc wcf i j
    | (false,true) -> draw_mine_cc wcf i j
    | (false,false) -> draw_num_cc wcf i j cell.nbm ;;
    val draw_cell_end : window_config -> cell array array -> int -> int -> unit =
    <fun>

    Despliegue de los demas componentes

    El estado del modo de abanderado es indicado por un cuado que esta en la parte de abajo o en la parte de arriba y contiene o la palabra encendido o apagado (ON o OFF).


    # let draw_flag_switch wcf on =
    if on then wcf.flag_box.r <- Bot else wcf.flag_box.r <- Top ;
    draw_box wcf.flag_box ;
    if on then draw_string_in_box Center "ON" wcf.flag_box Graphics.red
    else draw_string_in_box Center "OFF" wcf.flag_box Graphics.blue ;;
    val draw_flag_switch : window_config -> bool -> unit = <fun>

    Ahora desplegamos el proposito del boton abanderado arriba


    # let draw_flag_title wcf =
    let m = "Flagging" in
    let w,h = Graphics.text_size m in
    let x = (wcf.main_box.w-w)/2
    and y0 = wcf.dialog_box.y+wcf.dialog_box.h in
    let y = y0+(wcf.main_box.h-(y0+h))/2
    in Graphics.moveto x y ;
    Graphics.draw_string m ;;
    val draw_flag_title : window_config -> unit = <fun>

    Durante el juego, el numero de celdas vacias final para ser abiertas y el numero de celda para poner marca son desplegados en un cuadro de dialogo, de izquierda a derecha del boton de modo de flagging.


    # let print_score wcf nbcto nbfc =
    erase_box wcf.d1_box ;
    draw_string_in_box Center (string_of_int nbcto) wcf.d1_box Graphics.blue ;
    erase_box wcf.d2_box ;
    draw_string_in_box Center (string_of_int (wcf.cf.nbmines-nbfc)) wcf.d2_box
    ( if nbfc>wcf.cf.nbmines then Graphics.red else Graphics.blue ) ;;
    val print_score : window_config -> int -> int -> unit = <fun>

    • Pag. 12

    Para dibujar el campo de mina inicial, necesitamos dibujar (numero de renglones) x (numero de columnas) tiempos para cerrar la misma celda. Siempre es el mismo dibujo, pero tal vez tome mas tiempo, es tan necesario dibujar un rectangulo asi como 4 trapezoides. Para agilizar este comienzo, dibujamos solo una celda, tomar el mapa de bit correspondiente al dibujo y pegarlo en cada celda.


    # let draw_field_initial wcf =
    draw_closed_cc wcf 0 0 ;
    let cc = wcf.current_box in
    let bitmap = draw_box cc ; Graphics.get_image cc.x cc.y cc.w cc.h in
    let draw_bitmap (i,j) = let x,y=wcf.cell (i,j)
    in Graphics.draw_image bitmap x y
    in iter_cells wcf.cf draw_bitmap ;;
    val draw_field_initial : window_config -> unit = <fun>

    Al final del juego, abrimos todo el campo de minas mientras ponemos una cruz roja en cada una de las celdas mal elegidas.


    # let draw_field_end wcf bd =
    iter_cells wcf.cf (fun (i,j) -> draw_cell_end wcf bd i j) ;;
    val draw_field_end : window_config -> cell array array -> unit = <fun>

    Finalmente, la funcion central desplegable colocada al principio del juego abre el contexto de graficas y muestra el estado inicial de los componentes.


    # let open_wcf wcf =
    Graphics.open_graph ( " " ^ (string_of_int wcf.main_box.w) ^ "x" ^
    (string_of_int wcf.main_box.h) ) ;
    draw_box wcf.main_box ;
    draw_box wcf.dialog_box ;
    draw_flag_switch wcf false ;
    draw_box wcf.field_box ;
    draw_field_initial wcf ;
    draw_flag_title wcf ;
    print_score wcf ((wcf.cf.nbrows*wcf.cf.nbcols)-wcf.cf.nbmines) 0 ;;
    val open_wcf : window_config -> unit = <fun>

    Notece qe todos los despliegues primarios tienen parametros dados por una configuracion grafica de tipo window_config. Esto los hace independientes del layout de los componentes de nuestro “buscaminas”. Si nosotros...........

    Si deseamos modificar la disposición, el código se sigue utilizando sin ninguna modificación, sólo la configuración necesita ser puesta al día.

    Interacción con el jugador

    Ahora enumeramos lo que puede hacer el jugador:

    • él puede hacer click en la caja del modo para cambiar modo (abertura o el señalar por medio de una bandera),

    • él puede hacer click en una celda para abrirla o para señalarla por medio de una bandera,

    • él puede golpear la llave de ' q ' para parar el juego.

    Recuerde que un acontecimiento gráfico (Graphics.event) se debe asociar a un expediente (Graphics.status) que contenga la información actual sobre el mouse y el teclado cuando ocurre el acontecimiento. Una interacción con el mouse puede suceder en el botón del modo, o en una celda del campo de la mina. Cada otro acontecimiento del mouse debe ser ignorado. Para distinguir estos acontecimientos del mouse creamos el tipo:


    # type clickon = Out | Cell of (int*int) | SelectBox ;;

    También, presionar el botón del mouse y lanzarlo son dos diversos acontecimientos. Para que un click sea válido, requerimos que ambos acontecimientos ocurren en el mismo componente (el botón del modo que se señala por medio de una bandera o una celda del campo de la mina).

    # let locate_click wcf st1 st2 =
    let clickon_of st =
    let x = st.Graphics.mouse_x and y = st.Graphics.mouse_y
    in if x>=wcf.flag_box.x && x<=wcf.flag_box.x+wcf.flag_box.w &&
    y>=wcf.flag_box.y && y<=wcf.flag_box.y+wcf.flag_box.h
    then SelectBox
    else let (x2,y2) = wcf.coor (x,y)
    in if x2>=0 && x2<wcf.cf.nbcols && y2>=0 && y2<wcf.cf.nbrows
    then Cell (x2,y2) else Out
    in
    let r1=clickon_of st1 and r2=clickon_of st2
    in if r1=r2 then r1 else Out ;;
    val locate_click :
    window_config -> Graphics.status -> Graphics.status -> clickon = <fun>

    El corazón del programa es el acontecimiento que se esta esperando y procesando, es definido en el lazo de la función. Es similar a la página descrita skel de la función??, pero especifica los acontecimientos del ratón más exacto. Los extremos del lazo cuando:

    • el jugador presiona el q o llave de Q, significando que él desea terminar el juego;

    • el jugador abre una célula que contiene una mina, después él pierde;

    • el jugador ha abierto toda la célula que es vacío, después él gana el juego.

    Recopilamos en un expediente de tipo minesw_cf la información útil para la interfaz:

    # type minesw_cf =
    { wcf : window_config; bd : cell array array;
    mutable nb_flagged_cells : int;
    mutable nb_hidden_cells : int;
    mutable flag_switch_on : bool } ;;

    El significado de los campos es:

    • wcf: la configuración gráfica;

    • bd: el tablero;

    • flag_switch_on: un boleano indicando si el modo que señala por medio de una bandera o el modo que se abre está encendido;

    • nb_flagged_cells: el número de celdas señaladas por medio de una bandera;

    • nb_hidden_cells: el número de celdas vacías faltan de abrirse;

    El lazo principal se pone en ejecución de esta manera:


    # let loop d f_init f_key f_mouse f_end =
    f_init ();
    try
    while true do
    let st = Graphics.wait_next_event
    [Graphics.Button_down;Graphics.Key_pressed]
    in if st.Graphics.keypressed then f_key st.Graphics.key
    else let st2 = Graphics.wait_next_event [Graphics.Button_up]
    in f_mouse (locate_click d.wcf st st2)
    done
    with End -> f_end ();;
    val loop :
    minesw_cf ->
    (unit -> 'a) -> (char -> 'b) -> (clickon -> 'b) -> (unit -> unit) -> unit =
    <fun>


    La función de la inicialización, el procesamiento del acontecimiento de limpieza de la función y del teclado son muy simples.


    # let d_init d () = open_wcf d.wcf
    let d_end () = Graphics.close_graph()
    let d_key c = if c='q' || c='Q' then raise End;;
    val d_init : minesw_cf -> unit -> unit = <fun>
    val d_end : unit -> unit = <fun>
    val d_key : char -> unit = <fun>

    Sin embargo, la función de proceso del acontecimiento del ratón requiere el uso de algunas funciones auxiliares:

    • flag_cell: al hacer click en una celda con modo que se señala por medio de una bandera encendido.

    • ending: al terminar el juego. Se revela el campo entero de la mina, exhibimos un mensaje que indica si el juego fue ganado o perdido, y esperamos al mouse o un acontecimiento del teclado para terminar el uso.

    • reveal: al hacer click en una celda con modo de la abertura encendido (es decir modo que señala por medio de una bandera apagado).


    # let flag_cell d i j =
    if d.bd.(i).(j).flag
    then ( d.nb_flagged_cells <- d.nb_flagged_cells -1;
    d.bd.(i).(j).flag <- false )
    else ( d.nb_flagged_cells <- d.nb_flagged_cells +1;
    d.bd.(i).(j).flag <- true );
    draw_cell d.wcf d.bd i j;
    print_score d.wcf d.nb_hidden_cells d.nb_flagged_cells;;
    val flag_cell : minesw_cf -> int -> int -> unit = <fun>

    # let ending d str =
    draw_field_end d.wcf d.bd;
    erase_box d.wcf.flag_box;
    draw_string_in_box Center str d.wcf.flag_box Graphics.black;
    ignore(Graphics.wait_next_event
    [Graphics.Button_down;Graphics.Key_pressed]);
    raise End;;
    val ending : minesw_cf -> string -> 'a = <fun>

    # let reveal d i j =
    let reveal_cell (i,j) =
    d.bd.(i).(j).seen <- true;
    draw_cell d.wcf d.bd i j;
    d.nb_hidden_cells <- d.nb_hidden_cells -1
    in
    List.iter reveal_cell (cells_to_see d.bd d.wcf.cf (i,j));
    print_score d.wcf d.nb_hidden_cells d.nb_flagged_cells;
    if d.nb_hidden_cells = 0 then ending d "WON";;
    val reveal : minesw_cf -> int -> int -> unit = <fun>

    La función de proceso del acontecimiento del ratón empareja un valor del tipo clickon.


    # let d_mouse d click = match click with
    Cell (i,j) ->
    if d.bd.(i).(j).seen then ()
    else if d.flag_switch_on then flag_cell d i j
    else if d.bd.(i).(j).flag then ()
    else if d.bd.(i).(j).mined then ending d "LOST"
    else reveal d i j
    | SelectBox ->
    d.flag_switch_on <- not d.flag_switch_on;
    draw_flag_switch d.wcf d.flag_switch_on
    | Out -> () ;;
    val d_mouse : minesw_cf -> clickon -> unit = <fun>

    Para crear una configuración del juego, tres parámetros son necesarios: el número de columnas, el número de filas, y el número de minas.


    # let create_minesw nb_c nb_r nb_m =
    let nbc = max default_config.nbcols nb_c
    and nbr = max default_config.nbrows nb_r in
    let nbm = min (nbc*nbr) (max 1 nb_m) in
    let cf = { nbcols=nbc ; nbrows=nbr ; nbmines=nbm } in
    generate_seed () ;
    let wcf = make_wcf cf in
    { wcf = wcf ;
    bd = initialize_board wcf.cf;
    nb_flagged_cells = 0;
    nb_hidden_cells = cf.nbrows*cf.nbcols-cf.nbmines;
    flag_switch_on = false } ;;
    val create_minesw : int -> int -> int -> minesw_cf = <fun>

    La función del lanzamiento crea una configuración según los números de las columnas, filas, y las minas, antes de llamar el lazo de proceso principal del acontecimiento.


    # let go nbc nbr nbm =
    let d = create_minesw nbc nbr nbm in
    loop d (d_init d) d_key (d_mouse d) (d_end);;
    val go : int -> int -> int -> unit = <fun>

    La llamada de la función va 10 10 10 estructuras y comienzos un juego del mismo tamaño que el representó en el cuadro 6.5.