24/1/17 지뢰찾기 (mine search)
2024. 1. 18. 09:00ㆍ카테고리 없음
<MineSearchPage.jsx> 심화주차 팀원분의 게임코드를 보며 한수 배워보자
import React from "react";
import MineSearch from "../components/mineSearch/MineSearch";
const MineSearchPage = () => { //실제 주소값 갖는 페이지이다.
return (
<div>
<MineSearch /> //실제 화면에 보여줄 컴포넌트
</div>
);
};
export default MineSearchPage;
<MineSearch.jsx>보자보자
import { createContext, useReducer, useMemo, useEffect } from "react";
import Form from "./Form"; // 컴포넌트로 추정
import Table from "./Table"; //이하동문
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; //우와 폰트 어썸이다. 이거 좋다던데
import { faStopwatch, faSmileWink } from "@fortawesome/free-solid-svg-icons"; //뭔지 모르지만 폰트어썸의
import styled from "styled-components"; //라이브러리적 뭐 있겄지 일단 기억
const MineSearch = () => {
const [state, dispatch] = useReducer(reducer, initialState); //오오오 유즈 리듀서다 이걸쓰시다니오오오
const { tableData, halted, timer, result } = state; //스테이트가 객체형이나 보네요 안의 값들 ㅇㅋ
// useEffect를 통해 타이머를 1초마다 dispatch
useEffect(() => { //유즈이펙트 조건부로 작동하시겠구먼
let timer;
if (!halted) { //halted가 없으면
timer = setInterval(() => {
dispatch({ type: INCREMENT_TIMER }); //이건 유즈리듀서의 디스패치로 보이는구먼
}, 1000); //작동시간은 1초뒤
}
return () => {
clearInterval(timer); //언마운트시에 타이머 꺼주고
};
}, [halted]); //디펜던시어레이에 halted의 변화를 구독하고 있군
//useMemo로 캐싱한 contextAPI를 사용하여 계속되는 렌더링 방지
const value = useMemo( //useMemo로 객체의 변화를 통제하시는군
() => ({ tableData: tableData, halted: halted, dispatch }), //객체에 디스패치도 들어있네?
[tableData, halted] // 디펜던시의 요 두값이 변화할때만 객체를 바꾸는군
);
return (
//value = {{ tableData: state.tableData, dispatch }} --> useMemo로 캐싱
<TableContext.Provider value={value}>
<Form /> //폼컨포넌트 여기 있고
<StTimer>
<FontAwesomeIcon icon={faStopwatch} /> {timer} //아하 위에서 사용하는 폰트어썸의 사용 방법이구먼
</StTimer> //시간도 보여주는거 같고
<Table /> //테이블 컴포넌트는 여기있고
{state.isWin ? ( //state의 속성중에 isWin 도 있나보구먼
<StWin class="result-win">
<FontAwesomeIcon icon={faSmileWink} /> {result} //icon속성에 폰트어썸의 어떤값을 넘겨줘서
</StWin> //어떠한 이미지를 보여주시는 거같은데
) : (
<StFail class="result-fail">{result}</StFail> //result 값을 보여주고
)}
</TableContext.Provider> //오호 여기에 유즈 컨텍스트가 있네??? 아하 유즈리듀서랑 유즈 컨텍스트의
); //복합작용으로 전역관리를 하시는 건가? 그러면 그냥 툴킷쓰시는게 낫지 않나?
}; //암튼 간지다.
export default MineSearch;
// styled-components //
const StTimer = styled.div`
margin-top: 30px;
display: flex;
justify-content: center;
font-size: 40px;
gap: 20px;
.svg-inline--fa {
font-size: inherit;
}
`;
const StWin = styled.div`
font-size: 30px;
font-weight: bold;
color: #000000;
color: orange;
.fa-smile-wink {
font-size: 50px;
}
`;
const StFail = styled.div`
font-size: 30px;
font-weight: bold;
color: #b52020;
`;
const StP = styled.p`
display: flex;
justify-content: center;
color: black;
margin-top: 50px;
`;
//////////////////////// //홀리 보통 스타일드 컴포넌트에서 끝인줄 알았지만밑에 뭔가 많다.
// 초기값 세팅 (createContext)
export const TableContext = createContext({ //유즈 컨택스트를 여기서 만드셨군 객체형이네
tableData: [], //테이블 데이터는 배열형태로 받으시고
dispatch: () => {}, //디스패치라는 함수도 갖고
halted: true, //홀티드는 정지란 뜻이니까 뭔가 멈추는 상태값인거 같네
});
// 승리 조건 설정
const initialState = { //초기값이구먼
tableData: [],
timer: 0,
result: "",
halted: true,
gameData: { //행과 cell?과 지뢰
row: 0,
cell: 0,
mine: 0,
},
isWin: false, //이긴상태 유무 판별용 같고
};
export const START_GAME = "START_GAME"; //이거 유즈 리듀서 쓰기위한 초기 type설정때
export const OPEN_CELL = "OPEN_CELL"; //상수 만들어 놓으신거 같고
export const CLICK_MINE = "CLICK_MINE";
export const FLAG_CELL = "FLAG_CELL";
export const QUESTION_CELL = "QUESTION_CELL";
export const NORMAL_CELL = "NORMAL_CELL";
export const INCREMENT_TIMER = "INCREMENT_TIMER";
const reducer = (state, action) => { //우와와!! 감동 진짜 유즈 리듀서다 와 쓰는사람 첨봄 멋있어
switch (action.type) { //액션타입의 종류로 구분 ㅇㅋㅇㅋ 리덕스랑 비슷해
case START_GAME: //케이스 설정하고
return { //바로 스테이트 반환하시는군
...state,
tableData: plantMine(action.row, action.cell, action.mine), //액션페이로드가 아니라 액션에
halted: false, // 각속성이 그냥 있네? 유즈리듀서는 원래 이렇게 하나?
gameData: {
row: action.row,
cell: action.cell, //여기 키값에 넣어주고
mine: action.mine,
},
timer: 0,
result: "",
};
case OPEN_CELL: { //이거는 셀을 눌렀을때 조건 같구먼
const tableData = [...state.tableData]; //테이블 데이터를 선언해서 할당해주고
//tableData[action.row] = [...state.tableData[action.row]];
let openCount = 0; //오픈카운트 0이라 이건 어따쓰지?
tableData.forEach((row, i) => { //배열매서드 포이치 쓰고 row 가 요소네?
tableData[i] = [...row]; //테이블데이터의 각 요소에 row를 복사 붙여넣기? 왜? 이유가 있겄지
}); // 모든 칸을 새로운 객체로 만들어 준다. //요기 써있네
const checked = [];
const checkAround = (row, cell) => { //요함수는 row와 cell을 받아서
// 상하 좌우 필터링
if (
row < 0 ||
row >= tableData.length ||
cell < 0 ||
cell >= tableData[0].length
) {
return; //조건 만족못하면 멈추고 //이미 연거 누를떄 작동인가?
}
// 못여는 칸 필터링
if (
[
CODE.FLAG,
CODE.FLAG_MINE,
CODE.OPENED,
CODE.QUESTION,
CODE.QUESTION_MINE,
].includes(tableData[row][cell]) //배열매서드의 includes를 찾아봐서 있으면 true
) {
return; //이떄도 그냥 끝내고
}
// 중복 체크
if (checked.includes(row + "," + cell)) { //체크드도 배열이고 소괄호값있으면
return; //걍 종료
} else {
checked.push(row + "," + cell); //종료로직들 다 통과하면 아까 빈배열이었던checked에
} //이걸 넣어주네?
let around = []; // 주변의 상태 값을 담는다
if (tableData[row - 1]) { //tableData의 특정요가 존재하면 이프문 발동
// 양 옆이 없을 때는 undefined를 배열에 담는다
around = around.concat([ //콘캣은 소괄호 안에 배열들을 한 배열로 만들어준다던데?
tableData[row - 1][cell - 1],
tableData[row - 1][cell],
tableData[row - 1][cell + 1],
]);
}
around = around.concat([ //요기도 콘캣으로 합쳐주는거 같고
tableData[row][cell - 1],
tableData[row][cell + 1],
]);
if (tableData[row + 1]) {
around = around.concat([ //요기도
tableData[row + 1][cell - 1],
tableData[row + 1][cell],
tableData[row + 1][cell + 1],
]);
}
const count = around.filter((v) => //로직 왜이렇게 많음?
[CODE.FLAG_MINE, CODE.MINE, CODE.QUESTION_MINE].includes(v)
).length; //around라는 배열을 필터해서 파란글씨 애들 에 v가 포함된애들로 배열만들고
//그 길이값을 갖는 count if (count === 0) {
if (row > -1) { //카운트가 0이고 row가 -1이상이면 near라는 배열 만들고
const near = [];
if (row - 1 > -1) { //row보다 1적은애가 -1이상이면 near에 뭘 채우네
near.push([row - 1, cell - 1]);
near.push([row - 1, cell]);
near.push([row - 1, cell + 1]);
}
near.push([row, cell - 1]);
near.push([row, cell + 1]); //얘들은 조건 없이 그냥 채우고
if (row + 1 < tableData.length) {
near.push([row + 1, cell - 1]);
near.push([row + 1, cell]);
near.push([row + 1, cell + 1]); //얘들도 길이조건보다 적으면 채우고
}
near.forEach((n) => {
if (tableData[n[0]][n[1]] !== CODE.OPENED) {
checkAround(n[0], n[1]); //이제 그 near를 돌려서 code.opened와 같으면
} //check어라운드라는 함수? 자동 인자는 두개 흐음
});
}
}
tableData[row][cell] = count; //카운트를 tableData에 할당해주고
};
checkAround(action.row, action.cell); //요 함수에 action의 속성 두개 넣어주고
tableData.forEach((tr, i) => { //이중 포이치문??? ㄷㄷ
tr.forEach((td, j) => { //이거 가로세로 로직때문인거 같은데? 아닌가?
if (td >= 0) {
openCount += 1; //카운트 올려주고
}
});
});
let halted = false; //여기서 이것들 선언해 주시네?
let result = "";
let isWin = false;
if (
openCount ===
state.gameData.row * state.gameData.cell - state.gameData.mine //오픈카운트와 이제 남은 칸들의 갯수비교로
) { //게임이 종료된건지 확인하는거군
halted = true;
result = <StP> {state.timer}초만에 성공하셨습니다 !! </StP>;
isWin = true; //이겼다는 상태로 바꿔주고
}
return {
...state,
tableData, //여기리턴문 있네 솔직히 어디서 시작했는지 기억안나 ㅋㅋㅋ
halted, //암튼 객체 반환 아하 케이스가 게임시작일때 로직의 반환이 이제 끝난거구나 ㅎㄷㄷ
result,
};
}
case CLICK_MINE: { //지뢰밟았을경우
const tableData = [...state.tableData]; //이건 동일하고
tableData[action.row] = [...state.tableData[action.row]]; //흐음 이것도 스프레드로 배열다시 만들어주고
tableData[action.row][action.cell] = CODE.CLICKED_MINE; //요고는 row번째애의 cell번째에의
//를 mine으로 할당하는 거구나 for (let i = 0; i < state.gameData.row; i++) {
for (let j = 0; j < state.gameData.cell; j++) { //이중 포문???
if (
tableData[i][j] === CODE.MINE || //전체 테이블에 지뢰가 있는곳 플래그가 된곳따져서
tableData[i][j] === CODE.FLAG_MINE
) {
tableData[i][j] = CODE.CLICKED_MINE; //지뢰를 누른걸로 할당?
}
}
}
return {
...state,
tableData, //clickmine에 대한 리턴이고
halted: true,
};
}
case FLAG_CELL: { //플래그 셀ㄷㄷ 셀 하나의 상태가 여러개라 조건도 많구먼
const tableData = [...state.tableData];
tableData[action.row] = [...state.tableData[action.row]];
tableData[action.row][action.cell] =
tableData[action.row][action.cell] === CODE.MINE //특정 셀이 코드 마인이니?
? CODE.FLAG_MINE //맞으면 플래그 마인하고
: CODE.FLAG; //아니면 코드 플래그해라
return {
...state,
tableData, //플래그셀의 리턴문
};
}
case QUESTION_CELL: { //물음표셀?이라고
const tableData = [...state.tableData];
tableData[action.row] = [...state.tableData[action.row]]; //한줄을 요 줄로 할당하고
tableData[action.row][action.cell] =
tableData[action.row][action.cell] === CODE.FLAG_MINE //셀하나의 할당의 조건은
? CODE.QUESTION_MINE //코드 플래그마인이면 퀘스쳔마인이고 아니면 걍 퀘스쳔
: CODE.QUESTION;
return {
...state,
tableData, //그에 따른 리턴
};
}
case NORMAL_CELL: { //그냥 셀은 당연히 있겠고 퀘스쳔 셀은 도대체 머여
const tableData = [...state.tableData];
tableData[action.row] = [...state.tableData[action.row]]; //위에서 보던애
tableData[action.row][action.cell] =
tableData[action.row][action.cell] === CODE.QUESTION_MINE
? CODE.MINE
: CODE.NORMAL; //퀘스쳔 셀과 비슷해
return {
...state,
tableData, //그에 따른 리턴
};
}
case INCREMENT_TIMER: { //타이머 증가로직으로 보이고
return {
...state,
timer: state.timer + 1, //스테이트들은 그냥 두고 timer들만 올리는 구먼
};
}
default:
return state; //디폴트값
}
};
export const CODE = { //오호 CODE라는 객체가 제일 밑에 있었구먼
MINE: -7, // 지뢰
NORMAL: -1, // 일반
QUESTION: -2, // 물음표
FLAG: -3, // 깃발
QUESTION_MINE: -4, // 지뢰 있는 칸의 물음표
FLAG_MINE: -5, // 지뢰 있는 칸의 깃발
CLICKED_MINE: -6, // 지뢰 클릭
OPENED: 0, // 정상적으로 오픈한 칸 -> 0 이상이면 전부 오픈
};
const plantMine = (row, cell, mine) => { //지뢰 심나보네
const data = [];
const shuffle = []; //그냥 데이터와 셔플하는애로 보이는 두 배열
// 난수 생성 후 셔플 리스트에 넣어주기
let i = 0;
while (i < mine) { //와일문으로 돌아주는군 마인의 갯수과 같아 질떄까지
const chosen = Math.floor(Math.random() * (row * cell)); //랜덤으로 row와 cell을 섞는군
if (shuffle.includes(chosen) === false) { //셔플안에 쵸즌이 존재안하면
shuffle.push(chosen); //쵸즌 넣어주고 증가
i++;
}
}
// 데이타 맵을 전부 노멀로 초기화
for (let i = 0; i < row; i++) { //여기도 이중포문이네
const rowData = [];
data.push(rowData);
for (let j = 0; j < cell; j++) { //각 로우의 셀을 CODE노멀로 채우는구먼
rowData.push(CODE.NORMAL);
}
}
// 가로 세로 계산해서 지뢰 심기
for (let k = 0; k < shuffle.length; k++) { //와 미쳤다. 뭐냐 이건
const ver = Math.floor(shuffle[k] / cell);
const hor = shuffle[k] % cell;
data[ver][hor] = CODE.MINE;
}
return data;
};
//와 진짜 돌았다 그냥 내려가면서 해석하기에는 무리가 있네요 로직들 엄청 많네요 지뢰찾기 2탄 기대해주세요.
//로직 좀더 들여다 볼꼐요