Borrow Concept Minesweeper

Hi,

I am currently trying to wrap my head around some of the rust basics by implementing a simple minesweeper in rust. This is my code so far:

extern crate rand;
use rand::Rng;

struct Cell{
    x: u32,
    y: u32,
    neighbour_bombs: u8,
    is_bomb: bool,
    revealed: bool
}

impl Cell
{
    fn new(x_pos: u32, y_pos: u32) -> Cell
    {
        Cell {
            x:x_pos,
            y:y_pos,
            neighbour_bombs:0u8,
            is_bomb:false,
            revealed:false
        }
    }
}

static WIDTH: u32 = 10;
static HEIGHT: u32 = 10;

fn get_cell_by_pos(x_pos:u32,y_pos:u32, cells: &mut Vec<Cell>) -> Option<&mut Cell>
{
    Some(&mut cells[(y_pos*WIDTH+x_pos) as usize])
}



fn get_neighbours<'a>(cell: &Cell, field:  &'a mut Vec<Cell>) -> Vec<&'a mut Cell>
{
    let offsets : [(i32,i32);8] = [(-1,-1),(0,-1),(1,-1),(-1,0),(1,0),(-1,1),(0,1),(1,1)];
    field.iter_mut().filter(|c| offsets.iter().any(|o| cell.x as i32 == c.x as i32 + o.0 as i32 && cell.y as i32 == c.y as i32 + o.1 as i32)).collect()
} 

fn generate_game_field(bomb_count: u32) -> Vec<Cell>
{
    let mut field: Vec<Cell> = vec![];
    for w in 0..WIDTH {
        for y in 0..HEIGHT
        {
            field.push(Cell::new(w, y));
        }
    }

    let mut placed_bombs : u32 = 0;
    let mut rng = rand::thread_rng();

    while placed_bombs < bomb_count
    {
        let c : &mut Cell = field.iter_mut().nth(rng.gen_range(0, WIDTH*HEIGHT) as usize).unwrap();
        if !c.is_bomb
        {
            c.is_bomb = true;
            get_neighbours(c, &mut field);
            placed_bombs+=1;
        }
    }

    return field;
}

I am building a vector of cells objects and after that I want to randomly place bombs and increase for all of their neighbours their neighbour bomb counter by one. So on the one hand I need to set the is_bomb for the current cell and also the bomb_neighbours for all of the elements returned by get_neighbours(). I thought that I both need them to be mutable. When building I get this error:

   |
61 |         let c : &mut Cell = field.iter_mut().nth(rng.gen_range(0, WIDTH*HEIGHT) as usize).unwrap();
   |                             ----- first mutable borrow occurs here
...
65 |             get_neighbours(c, &mut field);
   |             --------------    ^^^^^^^^^^ second mutable borrow occurs here
   |             |
   |             first borrow later used by call

Can someone explain me how this can be resolved? I am stuck here for hours now.

Not sure how "rusty" it is, but you can do this:

let mut c : Cell = *field.iter_mut().nth(rng.gen_range(0, WIDTH*HEIGHT) as usize).unwrap();
        if !c.is_bomb
        {
            c.is_bomb = true;
            get_neighbours(&c, &mut field);
            placed_bombs+=1;
        }

Hi, you can change the definition of get_neighbours to get_neighbours(x: u32, y: u32, field: &mut Vec<Cell>) -> Vec<&mut Cell> this would avoid the second borrow.

If your Cell struct doesn't have any other fields in your real code you could make it Copy, you would avoid many issues.

Couple of notes:

  • if you use the edition 2018 you can remove extern crate rand;
  • return at the end of a function isn't necessary (and recommended against)

Thanks this was a nice and easy solution to this, I wonder why I thought I needed to pass in the cell when all I wanted where the coordinates.

Now on the reveal function I face a similar issue:

Summary
fn reveal(x_pos: u32, y_pos: u32, field: &mut Vec<Cell>) -> bool 
{
let  c : &mut Cell = &mut field[(y_pos*WIDTH+x_pos) as usize];
c.revealed = true;
if c.is_bomb {
    return true;
}        

for neighbour in get_neighbours(c.x, c.y, field).iter()
{
    if !neighbour.is_bomb
    {
        reveal(neighbour.x, neighbour.y, field);
    }
}

return false;
}

I need to borrow twice here again

You could have a function get_neighbours_positions that returns [u32; 2] or tuples instead of Cells.
You could make a Vec of positions after calling get_neighbours and iterate on it.
It could also be fixed by making Cell Copy.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.