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)