DEV Community

Cover image for Vue 3 vs React: The Minesweeper game
roggc
roggc

Posted on

Vue 3 vs React: The Minesweeper game

In this post I will try to compare the two frameworks by developing in each of them the minesweeper game.

Here is the code for the minesweeper game with React:

import { useEffect, useRef, useState } from 'react'
import Minesweeper from './Minesweeper'

const App = () => {
  const initialDim = 2

  const [dim, setDim] = useState(initialDim)
  const [Dim, SetDim] = useState(initialDim)

  const [foo, setFoo] = useState(0)

  const [minesweeper, setMinesweeper] = useState(
    <Minesweeper key={Math.random()} dim={Dim} />
  )

  const reset = () => {
    setMinesweeper(<Minesweeper key={Math.random()} dim={Dim} />)
  }

  const inputRef = useRef(null)

  const setDimension = () => {
    SetDim(inputRef.current.value)
    setFoo((foo) => foo + 1)
  }

  useEffect(() => {
    reset()
  }, [foo])

  return (
    <div>
      {minesweeper}
      <button onClick={reset}>reset</button>
      <input
        type="text"
        value={dim}
        ref={inputRef}
        onChange={(event) => {
          setDim(event.target.value)
        }}
      />
      <button onClick={setDimension}>set dim</button>
    </div>
  )
}

export default App

Enter fullscreen mode Exit fullscreen mode

Previous was code for App.js. We have also Minesweeper.js and Cell.js (also for Vue 3).

import styled from 'styled-components'
import Cell from './Cell'
import { useEffect, useRef, useState } from 'react'

const Minesweeper = ({ dim }) => {
  const arrayRefs = new Array(dim)

  for (let i = 0; i < dim; i++) {
    arrayRefs[i] = new Array(dim)
  }

  const _minesAround = new Array(dim)
  for (let i = 0; i < dim; i++) {
    _minesAround[i] = new Array(dim)
  }

  const [minesAround, setMinesAround] = useState(_minesAround)

  const computeMinesAround = () => {
    for (let i = 0; i < dim; i++) {
      for (let j = 0; j < dim; j++) {
        let numOfMines = 0
        infoRef.current[i - 1] &&
          infoRef.current[i - 1][j - 1] &&
          infoRef.current[i - 1][j - 1].isMined &&
          numOfMines++
        infoRef.current[i - 1] &&
          infoRef.current[i - 1][j] &&
          infoRef.current[i - 1][j].isMined &&
          numOfMines++
        infoRef.current[i - 1] &&
          infoRef.current[i - 1][j + 1] &&
          infoRef.current[i - 1][j + 1].isMined &&
          numOfMines++
        infoRef.current[i] &&
          infoRef.current[i][j - 1] &&
          infoRef.current[i][j - 1].isMined &&
          numOfMines++
        infoRef.current[i] &&
          infoRef.current[i][j + 1] &&
          infoRef.current[i][j + 1].isMined &&
          numOfMines++
        infoRef.current[i + 1] &&
          infoRef.current[i + 1][j - 1] &&
          infoRef.current[i + 1][j - 1].isMined &&
          numOfMines++
        infoRef.current[i + 1] &&
          infoRef.current[i + 1][j] &&
          infoRef.current[i + 1][j].isMined &&
          numOfMines++
        infoRef.current[i + 1] &&
          infoRef.current[i + 1][j + 1] &&
          infoRef.current[i + 1][j + 1].isMined &&
          numOfMines++
        const newMinesAround = [...minesAround]
        newMinesAround[i][j] = numOfMines
        setMinesAround(newMinesAround)
      }
    }
  }

  useEffect(() => {
    computeMinesAround()
  }, [])

  const infoRef = useRef(arrayRefs)

  const _cellRefs = new Array(dim)
  for (let i = 0; i < dim; i++) {
    _cellRefs[i] = new Array(dim)
  }

  const cellRefs = useRef(_cellRefs)

  // we prepare the board
  const board = new Array(dim)

  for (let i = 0; i < dim; i++) {
    board[i] = new Array(dim)
  }

  for (let i = 0; i < dim; i++) {
    for (let j = 0; j < dim; j++) {
      board[i][j] = (
        <Cell
          key={`${i}_${j}`}
          numOfMines={minesAround[i][j]}
          ref={(item) => (cellRefs.current[i][j] = item)}
          cellRefs={cellRefs}
          i={i}
          j={j}
          infoRef={infoRef}
        />
      )
    }
  }

  return (
    <Div>
      {board.map((row, i) => (
        <Row key={i}>{row}</Row>
      ))}
    </Div>
  )
}

export default Minesweeper

const Div = styled.div`
  font-family: sans-serif;
`

const Row = styled.div`
  display: flex;
`
Enter fullscreen mode Exit fullscreen mode

Previous was Minesweeper.js. Finally, for React, we have Cell.js:

import styled from 'styled-components'
import { useEffect, useState, forwardRef } from 'react'

const Cell = forwardRef(
  ({ numOfMines, cellRefs, i, j, infoRef }, ref) => {
    const [isCovered, setIsCovered] = useState(true)
    const [isMined,] = useState(Math.random() > 0.8)

    useEffect(() => {
      infoRef.current[i][j] = {isMined,isCovered}
    }, [isCovered,isMined])

    useEffect(() => {
      if (
        numOfMines === 0 &&
        isMined === false &&
        infoRef.current[i - 1] &&
        infoRef.current[i - 1][j - 1] &&
        infoRef.current[i - 1][j - 1].isCovered &&
        infoRef.current[i][j].isCovered === false
      ) {
        cellRefs.current[i - 1][j - 1].click()
      }
    }, [
      isCovered,
      numOfMines,
      isMined,
      infoRef.current[i - 1] &&
        infoRef.current[i - 1][j - 1] &&
        infoRef.current[i - 1][j - 1].isCovered,
      infoRef.current[i][j] && infoRef.current[i][j].isCovered,
    ])

    useEffect(() => {
      if (
        numOfMines === 0 &&
        isMined === false &&
        infoRef.current[i - 1] &&
        infoRef.current[i - 1][j] &&
        infoRef.current[i - 1][j].isCovered &&
        infoRef.current[i][j].isCovered === false
      ) {
        cellRefs.current[i - 1][j].click()
      }
    }, [
      isCovered,
      numOfMines,
      isMined,
      infoRef.current[i - 1] &&
        infoRef.current[i - 1][j] &&
        infoRef.current[i - 1][j].isCovered,
      infoRef.current[i][j] && infoRef.current[i][j].isCovered,
    ])

    useEffect(() => {
      if (
        numOfMines === 0 &&
        isMined === false &&
        infoRef.current[i - 1] &&
        infoRef.current[i - 1][j + 1] &&
        infoRef.current[i - 1][j + 1].isCovered &&
        infoRef.current[i][j].isCovered === false
      ) {
        cellRefs.current[i - 1][j + 1].click()
      }
    }, [
      isCovered,
      numOfMines,
      isMined,
      infoRef.current[i - 1] &&
        infoRef.current[i - 1][j + 1] &&
        infoRef.current[i - 1][j + 1].isCovered,
      infoRef.current[i][j] && infoRef.current[i][j].isCovered,
    ])

    useEffect(() => {
      if (
        numOfMines === 0 &&
        isMined === false &&
        infoRef.current[i] &&
        infoRef.current[i][j - 1] &&
        infoRef.current[i][j - 1].isCovered &&
        infoRef.current[i][j].isCovered === false
      ) {
        cellRefs.current[i][j - 1].click()
      }
    }, [
      isCovered,
      numOfMines,
      isMined,
      infoRef.current[i] &&
        infoRef.current[i][j - 1] &&
        infoRef.current[i][j - 1].isCovered,
      infoRef.current[i][j] && infoRef.current[i][j].isCovered,
    ])

    useEffect(() => {
      if (
        numOfMines === 0 &&
        isMined === false &&
        infoRef.current[i] &&
        infoRef.current[i][j + 1] &&
        infoRef.current[i][j + 1].isCovered &&
        infoRef.current[i][j].isCovered === false
      ) {
        cellRefs.current[i][j + 1].click()
      }
    }, [
      isCovered,
      numOfMines,
      isMined,
      infoRef.current[i] &&
        infoRef.current[i][j + 1] &&
        infoRef.current[i][j + 1].isCovered,
      infoRef.current[i][j] && infoRef.current[i][j].isCovered,
    ])

    useEffect(() => {
      if (
        numOfMines === 0 &&
        isMined === false &&
        infoRef.current[i + 1] &&
        infoRef.current[i + 1][j - 1] &&
        infoRef.current[i + 1][j - 1].isCovered &&
        infoRef.current[i][j].isCovered === false
      ) {
        cellRefs.current[i + 1][j - 1].click()
      }
    }, [
      isCovered,
      numOfMines,
      isMined,
      infoRef.current[i + 1] &&
        infoRef.current[i + 1][j - 1] &&
        infoRef.current[i + 1][j - 1].isCovered,
      infoRef.current[i][j] && infoRef.current[i][j].isCovered,
    ])

    useEffect(() => {
      if (
        numOfMines === 0 &&
        isMined === false &&
        infoRef.current[i + 1] &&
        infoRef.current[i + 1][j] &&
        infoRef.current[i + 1][j].isCovered &&
        infoRef.current[i][j].isCovered === false
      ) {
        cellRefs.current[i + 1][j].click()
      }
    }, [
      isCovered,
      numOfMines,
      isMined,
      infoRef.current[i + 1] &&
        infoRef.current[i + 1][j] &&
        infoRef.current[i + 1][j].isCovered,
      infoRef.current[i][j] && infoRef.current[i][j].isCovered,
    ])

    useEffect(() => {
      if (
        numOfMines === 0 &&
        isMined === false &&
        infoRef.current[i + 1] &&
        infoRef.current[i + 1][j + 1] &&
        infoRef.current[i + 1][j + 1].isCovered &&
        infoRef.current[i][j].isCovered === false
      ) {
        cellRefs.current[i + 1][j + 1].click()
      }
    }, [
      isCovered,
      numOfMines,
      isMined,
      infoRef.current[i + 1] &&
        infoRef.current[i + 1][j + 1] &&
        infoRef.current[i + 1][j + 1].isCovered,
      infoRef.current[i][j] && infoRef.current[i][j].isCovered,
    ])

    const uncover = () => {
      if (isCovered) {
        setIsCovered(false)
      }
    }

    return (
      <Div isCovered={isCovered} onClick={uncover} ref={ref}>
        {isCovered ? '' : isMined ? '😄' : numOfMines === 0 ? '' : numOfMines}
      </Div>
    )
  }
)

export default Cell

const Div = styled.div`
  border-radius: 5px;
  width: 18px;
  height: 18px;
  margin: 2px;
  cursor: pointer;
  ${({ isCovered }) => `
${isCovered ? 'background-color:grey;' : ''}
`}
`

Enter fullscreen mode Exit fullscreen mode

So that's it for React. Next we are going to see the same files for Vue 3:

import {defineComponent,ref} from 'vue'
import {Minesweeper} from './Minesweeper'

export const App=defineComponent(()=>{

    const inputRef=ref()

    const dim=ref()

    const setDimension=()=>{
        dim.value=inputRef.value.value
    }

    return ()=>{
        return <>
        <input ref={inputRef} /><button onClick={setDimension}>set Dimension</button>
        <Minesweeper dim={dim}/>
        </>
    }
})
Enter fullscreen mode Exit fullscreen mode

Previous was App.tsx. Next we are going to see Minesweeper.tsx for Vue 3:

import { defineComponent,ref,onMounted,onUpdated } from 'vue'
import {Cell} from './Cell'
import styled from 'vue3-styled-components'

interface IProps{
    dim:any;
}

export const Minesweeper= defineComponent((props:IProps)=>{

        const infoRefs=ref<any[]>(new Array(props.dim.value))

        onUpdated(()=>{
            for(let i=0;i<props.dim.value;i++){
                for(let j=0;j<props.dim.value;j++){
                    let minesAround=0
                    if(infoRefs.value[i-1]&&infoRefs.value[i-1][j-1])
                        infoRefs.value[i-1][j-1].isMined&&minesAround++
                    if(infoRefs.value[i-1]&&infoRefs.value[i-1][j])
                        infoRefs.value[i-1][j].isMined&&minesAround++
                    if(infoRefs.value[i-1]&&infoRefs.value[i-1][j+1])
                        infoRefs.value[i-1][j+1].isMined&&minesAround++
                    if(infoRefs.value[i]&&infoRefs.value[i][j-1])
                        infoRefs.value[i][j-1].isMined&&minesAround++
                    if(infoRefs.value[i]&&infoRefs.value[i][j+1])
                        infoRefs.value[i][j+1].isMined&&minesAround++
                    if(infoRefs.value[i+1]&&infoRefs.value[i+1][j-1])
                        infoRefs.value[i+1][j-1].isMined&&minesAround++
                    if(infoRefs.value[i+1]&&infoRefs.value[i+1][j])
                        infoRefs.value[i+1][j].isMined&&minesAround++
                    if(infoRefs.value[i+1]&&infoRefs.value[i+1][j+1])
                        infoRefs.value[i+1][j+1].isMined&&minesAround++
                    infoRefs.value[i][j]={...infoRefs.value[i][j],minesAround}
                }
            }
        })

        return ()=>{

            const board=new Array(props.dim.value)

            for(let i=0;i<props.dim.value;i++){
                infoRefs.value[i]=new Array(props.dim.value)
                board[i]=new Array(props.dim.value)
                for(let j=0;j<props.dim.value;j++){
                    infoRefs.value[i][j]={}
                    board[i][j]=<Cell key={`${props.dim.value}_${i}_${j}`} infoRefs={infoRefs} i={i} j={j} />
                }
            }

            return <>
                {board.map(row=><Row>{row}</Row>)}
            </>
        }
})

Minesweeper.props={
    dim:{
        type:Object
    }
}

const Row=styled.div`
display:flex;
`
Enter fullscreen mode Exit fullscreen mode

Finally, the last file for Vue 3 would be Cell.tsx:

import { defineComponent,onMounted,onUpdated,ref } from 'vue'
import styled from 'vue3-styled-components'

interface IProps{
    infoRefs:any;
    i:number;
    j:number;
}

export const Cell= defineComponent((props:IProps)=>{

    const isMined=ref(Math.random()>0.9)
    const cellRef=ref()

    onMounted(()=>{
        props.infoRefs.value[props.i][props.j]={...props.infoRefs.value[props.i][props.j],isMined,cellRef,isCovered}
    })

    onUpdated(()=>{
        if(!isCovered.value&&!isMined.value&&props.infoRefs.value[props.i][props.j].minesAround===0){
            props.infoRefs.value[props.i-1]&&props.infoRefs.value[props.i-1][props.j-1]&&props.infoRefs.value[props.i-1][props.j-1].isCovered&&props.infoRefs.value[props.i-1][props.j-1].cellRef.$el.click()
            props.infoRefs.value[props.i-1]&&props.infoRefs.value[props.i-1][props.j]&&props.infoRefs.value[props.i-1][props.j].isCovered&&props.infoRefs.value[props.i-1][props.j].cellRef.$el.click()
            props.infoRefs.value[props.i-1]&&props.infoRefs.value[props.i-1][props.j+1]&&props.infoRefs.value[props.i-1][props.j+1].isCovered&&props.infoRefs.value[props.i-1][props.j+1].cellRef.$el.click()
            props.infoRefs.value[props.i]&&props.infoRefs.value[props.i][props.j-1]&&props.infoRefs.value[props.i][props.j-1].isCovered&&props.infoRefs.value[props.i][props.j-1].cellRef.$el.click()
            props.infoRefs.value[props.i]&&props.infoRefs.value[props.i][props.j+1]&&props.infoRefs.value[props.i][props.j+1].isCovered&&props.infoRefs.value[props.i][props.j+1].cellRef.$el.click()
            props.infoRefs.value[props.i+1]&&props.infoRefs.value[props.i+1][props.j-1]&&props.infoRefs.value[props.i+1][props.j-1].isCovered&&props.infoRefs.value[props.i+1][props.j-1].cellRef.$el.click()
            props.infoRefs.value[props.i+1]&&props.infoRefs.value[props.i+1][props.j]&&props.infoRefs.value[props.i+1][props.j].isCovered&&props.infoRefs.value[props.i+1][props.j].cellRef.$el.click()
            props.infoRefs.value[props.i+1]&&props.infoRefs.value[props.i+1][props.j+1]&&props.infoRefs.value[props.i+1][props.j+1].isCovered&&props.infoRefs.value[props.i+1][props.j+1].cellRef.$el.click()
        }
    })


    const isCovered=ref(true)

    const unCover=()=>{
        isCovered.value=false
        props.infoRefs.value[props.i][props.j]={...props.infoRefs.value[props.i][props.j],isCovered}
        console.log(`clicked ${props.i}_${props.j}`)
    }

    return ()=>{

        return <>
        <_Cell isCovered={isCovered.value} onClick={unCover} ref={cellRef}>{isCovered.value?'':isMined.value?'😁':props.infoRefs.value[props.i][props.j].minesAround===0?'':props.infoRefs.value[props.i][props.j].minesAround}</_Cell>
        </>
    }

})

Cell.props={
    infoRefs:{
        type:Object
    },
    i:{
        type:Number
    },
    j:{
        type:Number
    }
}


const _CellProps={
    isCovered:Boolean
}

const _Cell=styled('div',_CellProps)`
${({isCovered}:any)=>`
${isCovered?`
background-color:grey;
`:''}
`}
width:18px;
height:18px;
border-radius:5px;
margin:5px;
cursor:pointer;
`
Enter fullscreen mode Exit fullscreen mode

So that was all. Thanks for watching.

ps: for those who want to check which one is more performant, copy the code, implement it in a project, run it, put a dimension of one hundred, and see what happens 😉 (you might get surprised)

ps2: keywords: vue 3, react, jsx, benchmark, minesweeper, performance

Top comments (0)