Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- 리액트
- React Component
- MongoDB
- 리액트스타디
- mongo
- NoSQL
- sementicversion
- 클래스컴포넌트
- sequelize
- 리액트컴포넌트
- nodejs교과서
- 시퀄라이즈
- node
- nodeJS
- React
- NPM
- mongoose
- 리액트기초
- component
- npm명령어
- express-generator
- 제로초예제
- 시퀄라이즈공부
Archives
- Today
- Total
개발노트
08 지뢰찾기 본문
import React, { useReducer, useMemo, useCallback, useRef, useEffect } from 'react';
import { createContext } from 'react';
import Table from './Table';
import Form from './Form';
/*
*/
export const CODE = {
OPEN_MINE: -11, //
OPEN: -1, //
CLOSE_MINE: -44,
CLOSE: -4,
FLAG_MINE: -22,
FLAG: -2,
QUESTION_MINE: -33,
QUESTION: -3,
}
function makeTableData(col, row) {
const data = new Array();
//console.log(col, row);
//let count = 1;
for (let i = 0; i < row; i++) {
const row = [];
for (let j = 0; j < col; j++) {
row.push(CODE.CLOSE);
//count++;
}
data.push(row);
}
return data;
}
function plantMine(data, mine) {
const col = data[0].length; //9
const row = data.length; // 7
let arr = data;
const mines = getRandomNums(mine, (row * col));
//console.log(mines);
mines.forEach((mine) => {
mine = mine - 1;
const one = Math.floor(mine / col);
const two = Math.floor(mine % col)
arr[one][two] = CODE.CLOSE_MINE;
});
//console.log(arr);
return arr;
}
function getRandomNums(count, max) {
const nums = new Set();
while (nums.size < count) {
nums.add(Math.floor((Math.random() * max) + 1));
}
return Array.from(nums).sort((a, b) => a - b);
}
function aroundMineNums(tableData, tr, td) {
let around = [];
if (tableData[tr - 1]) {
around = around.concat(
tableData[tr - 1][td - 1],
tableData[tr - 1][td],
tableData[tr - 1][td + 1]
);
}
around = around.concat(
tableData[tr][td - 1], tableData[tr][td + 1],
);
if (tableData[tr + 1]) {
around = around.concat(
tableData[tr + 1][td - 1],
tableData[tr + 1][td],
tableData[tr + 1][td + 1]
);
}
const mineCount = around.filter(
(v) => ([CODE.CLOSE_MINE, CODE.FLAG_MINE, CODE.QUESTION_MINE].includes(v))).length;
return mineCount;
}
const initialState = {
tableData: [],
timer: 0,
youWin: false,
gameOver: false,
mineTotal: 0,
cellTotal: 0,
cellOpend: 0, //
}
export const CLICK_MAKE_BTN = 'CLICK_MAKE_BTN';
export const CLICK_NORMAL = 'CLICK_NORMAL';
export const CLICK_MINE = 'CLICK_MINE';
export const R_CLICK_FLAG = 'R_CLICK_FLAG';
export const R_CLICK_CLOSE = 'R_CLICK_CLOSE';
export const R_CLICK_QUESTION = 'R_CLICK_QUESTION';
export const INCREMENT_TIMER = 'INCREMENT_TIMER';
const reducer = (state, action) => {
switch (action.type) {
case CLICK_MAKE_BTN: // START game...
return {
...state,
tableData: plantMine(makeTableData(action.col, action.row), action.mine),
cellTotal: (action.col * action.row) - action.mine,
cellOpend: 0,
mineTotal: action.mine,
youWin: false,
gameOver: false,
timer: 0,
}
case CLICK_NORMAL: {
// if (state.cellOpend + 1 === state.cellTotal
// && [CODE.CLOSE_MINE, CODE.FLAG_MINE, CODE.QUESTION_MINE].indexOf(tableData[action.tr][action.td]) !== -1) {
//}
//1. table데이터 복사.
const tableData = [...state.tableData];
tableData.forEach((tr, i) => { tableData[i] = [...tr] });
// CODE.OPEN;
//tableData[action.tr][action.td] = aroundMineNums(tableData, action.tr, action.td);
const checked = [];
let openedCount = 0;
const checkAround = (tr, td) => {
//상하좌우 없으면 안열기-그냥 리턴
if (tr < 0 || tr >= tableData.length || td < 0 || td >= tableData[0].length) {
return;
}
//console.log(([CODE.OPEN]).includes(tableData[tr][td]));
//오픈되있으면 리턴.
if ([CODE.FLAG, CODE.FLAG_MINE, CODE.QUESTION_MINE, CODE.QUESTION]
.includes(tableData[tr][td]) || -1 <= tableData[tr][td]) {
return;
} else {
// 한 번 연칸은 무시하기...
if (checked.includes(tr + '/' + td)) {
return;
} else {
checked.push(tr + '/' + td);
}
const count = aroundMineNums(tableData, tr, td); // 지뢰 없으면
// 타겟칸의 지뢰가 0 이면 주변칸 오픈
if (count === 0) {
if (tr > -1) {
const near = [];
if (tr - 1 > -1) {
near.push([tr - 1, td - 1]);
near.push([tr - 1, td]);
near.push([tr - 1, td + 1]);
}
near.push([tr, td - 1]);
near.push([tr, td + 1]);
if (tr + 1 < tableData.length) {
near.push([tr + 1, td - 1]);
near.push([tr + 1, td]);
near.push([tr + 1, td + 1]);
}
//오픈해줄 칸 담기
near.forEach((n) => {
if (tableData[n[0]][n[1]] !== CODE.OPEN) {
checkAround(n[0], n[1]); //재귀
}
})
}
}
tableData[tr][td] = count;
openedCount += 1;
// console.log(`------tr-${tr} td-${td} ` + openedCount);
}
}
checkAround(action.tr, action.td);
let win = false;
if (state.cellTotal - (state.cellOpend + openedCount) === 0) {
win = true;
}
return {
...state,
tableData,
youWin: win,
cellOpend: state.cellOpend + openedCount
}
}
case CLICK_MINE: {
const tableData = [...state.tableData];
tableData[action.tr] = [...tableData[action.tr]];
tableData[action.tr][action.td] = CODE.OPEN_MINE;
//나머지 지뢰인애도 값을 OPEN_MINE으로 바꿔주기~
return {
...state,
tableData,
gameOver: true
}
}
case R_CLICK_FLAG: { // flag => normal
const tableData = [...state.tableData];
tableData[action.tr] = [...tableData[action.tr]];
tableData[action.tr][action.td] = (tableData[action.tr][action.td] == CODE.FLAG_MINE)
? CODE.CLOSE_MINE : CODE.CLOSE;
return {
...state,
tableData
}
}
case R_CLICK_CLOSE: { //close => question
const tableData = [...state.tableData];
tableData[action.tr] = [...tableData[action.tr]];
tableData[action.tr][action.td] = (tableData[action.tr][action.td] == CODE.CLOSE_MINE)
? CODE.QUESTION_MINE : CODE.QUESTION;
return {
...state,
tableData
}
}
case R_CLICK_QUESTION: { // question => flag
const tableData = [...state.tableData];
tableData[action.tr] = [...tableData[action.tr]];
tableData[action.tr][action.td] = (tableData[action.tr][action.td] == CODE.QUESTION_MINE)
? CODE.FLAG_MINE : CODE.FLAG;
return {
...state,
tableData
}
}
case INCREMENT_TIMER: {
return {
...state,
timer: state.timer += 1
}
}
default:
return state
}
}
//contextApi로 관리할 데이터
export const TableContext = createContext({
tableData: [],
});
const MineSearch = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const { tableData, gameOver, cellTotal, cellOpend, youWin, timer } = state;
const value = useMemo(() => (
{ tableData, dispatch, gameOver, youWin, timer }
), [tableData]);
useEffect(() => {
let timer;
//처음 시작 막기 + 버튼 클릭시 시작
if (tableData.length < 1) {
return;
}
if (!gameOver && !youWin) {
timer = setInterval(() => {
dispatch({ type: INCREMENT_TIMER });
}, 1000);
} else {
clearInterval(timer);
}
return () => { clearInterval(timer); }
}, [tableData, gameOver, youWin]);
return (
<div className="mine-search-wrap">
<h2>지뢰찾기</h2>
<TableContext.Provider value={value}>
<Form />
<Table tableData={tableData} />
{cellTotal !== 0 ?
<ul>
<li>남은칸 {cellTotal - cellOpend}/{cellTotal}</li>
{youWin && <h2>{timer}초만에 승리!</h2>}
<li>{timer} 초</li>
</ul> : null}
</TableContext.Provider>
</div>
)
}
export default MineSearch;
import React, { useState, useCallback, useContext, memo } from 'react';
import { TableContext } from './MineSearch';
/*
-state로 row cell mine 값 저장하기
-input 마다 onChange 되면 state에 값을 set하기
-버튼 click시 dispatch로 3개 값 상위컴포넌트로 올려주기
Q.input컴포넌트도 컴포넌트 분리가능할것같은데.
Q. useRef 필수로 써야하는거 아니였나? focus기능같은거 안넣으니 필요없는건가;
reset할때 값 비워줘야 될때 안필요하나.?
Q. 제로초가 컴포넌트에 만드는 함수마다 useCallback으로 감싸는이유모르겠음;
=> 걍 컴포넌트 안에있는 이벤트 핸들러 함수들 다 감싸는게 맞는건가;; 네 맞슴다.
불필요한 렌더링 막기 위해서 그냥 컴포넌트 안에 있는 이벤트핸들러같은경우 useCallback
*/
const Form = memo(() => {
const value = useContext(TableContext);
const { dispatch, tableData } = useContext(TableContext);
const [col, setCol] = useState(5);
const [row, setRow] = useState(5);
const [mine, setMine] = useState(5);
const onChangeCol = useCallback((e) => {
setCol(e.target.value);
}, [col]);
const onChangeRow = useCallback((e) => {
setRow(e.target.value);
}, [row]);
const onChangeMine = useCallback((e) => {
setMine(e.target.value);
}, [mine]);
const onClickBtn = useCallback((e) => {
//console.log(value);
e.preventDefault();
//console.log(col, row, mine);
dispatch({ type: 'CLICK_MAKE_BTN', col: col, row: row, mine: mine });
}, [col, row, mine]);
return (
<form>
<ul>
<li>
가로 <input id="col" type="number"
value={col} onChange={onChangeCol} />
세로 <input id="row" type="number"
value={row} onChange={onChangeRow} />
지뢰 <input id="mine" type="number"
value={mine} onChange={onChangeMine} />
</li>
</ul>
<button onClick={onClickBtn}>만들기</button>
</form>
)
});
export default Form;
import React, { useCallback, useContext, memo, useMemo } from 'react';
import { TableContext } from './MineSearch';
import { CODE } from './MineSearch';
/*
( close, open, open-mine, mine, flag, question )
-1 =>텍스트 함수하면 => '' (이거는.. 재귀써서 근처에 지뢰세서 알려주는 함수 필요 이건 나중에 우선은)
*/
const getTdStyle = (gameOver, tdData, youWin) => {
console.log("getTdStyle");
if (youWin) {
switch (tdData) {
case CODE.OPEN_MINE:
case CODE.CLOSE_MINE:
case CODE.FLAG_MINE:
case CODE.QUESTION_MINE:
return 'flag'
case CODE.OPEN:
case CODE.CLOSE:
return 'open'
//return 'close'
case CODE.FLAG:
return 'flag';
case CODE.QUESTION:
return 'question'
default:
return 'open'
}
}
else if (gameOver) {
switch (tdData) {
case CODE.OPEN_MINE:
case CODE.CLOSE_MINE:
case CODE.FLAG_MINE:
case CODE.QUESTION_MINE:
return 'mine'
case CODE.OPEN:
case CODE.CLOSE:
return 'open'
//return 'close'
case CODE.FLAG:
return 'flag';
case CODE.QUESTION:
return 'question'
default:
return 'open'
}
} else {
switch (tdData) {
case CODE.OPEN_MINE:
return 'mine'
case CODE.OPEN:
return 'open'
case CODE.CLOSE:
case CODE.CLOSE_MINE:
return 'close'
case CODE.FLAG:
case CODE.FLAG_MINE:
return 'flag';
case CODE.QUESTION:
case CODE.QUESTION_MINE:
return 'question'
default:
return 'open'
}
}
}
const getTdText = (tdData) => {
switch (tdData) {
case CODE.FLAG:
case CODE.FLAG_MINE:
case CODE.OPEN:
case CODE.OPEN_MINE:
case CODE.CLOSE:
return '';
case CODE.CLOSE_MINE:
return '___';
case CODE.QUESTION:
case CODE.QUESTION_MINE:
return '?';
default:
return tdData == 0 ? '' : tdData;
}
}
const Td = memo(({ tr, td, tdData }) => {
//const value = useContext(TableContext);
const { dispatch, tableData, gameOver, youWin } = useContext(TableContext);
const onClickTd = useCallback(() => {
if (gameOver) { return; }
const cell = tableData[tr][td];
switch (cell) {
case CODE.OPEN:
case CODE.MINE:
case CODE.FLAG:
case CODE.FLAG_MINE:
break;
case CODE.CLOSE_MINE:
case CODE.QUESTION_MINE:
dispatch({ type: 'CLICK_MINE', tr: tr, td: td });
break;
case CODE.CLOSE:
case CODE.QUESTION:
dispatch({ type: 'CLICK_NORMAL', tr: tr, td: td });
break;
default:
console.warn(tr, td);
break;
}
}, [tableData[tr][td], gameOver]);
const onClickTdRight = useCallback((e) => {
e.preventDefault();
if (gameOver) { return; }
const cell = tableData[tr][td];
switch (cell) {
case CODE.OPEN:
case CODE.MINE:
break;
case CODE.FLAG_MINE:
case CODE.FLAG:
dispatch({ type: 'R_CLICK_FLAG', tr: tr, td: td });
break;
case CODE.CLOSE_MINE:
case CODE.CLOSE:
dispatch({ type: 'R_CLICK_CLOSE', tr: tr, td: td });
break;
case CODE.QUESTION_MINE:
case CODE.QUESTION:
dispatch({ type: 'R_CLICK_QUESTION', tr: tr, td: td });
break;
default:
console.warn(tr, td);
break;
}
}, [tableData[tr][td], gameOver, youWin]);
console.log("td rendering")
return useMemo(() => (
<td className={`tr${tr}-td${td} ${getTdStyle(gameOver, tdData, youWin)}`}
key={`${tr}-${td}`} onClick={onClickTd} onContextMenu={onClickTdRight}>
{getTdText(tdData)}</td>
), [tableData[tr][td]]);
});
export default Td;
import React, { memo } from 'react';
import Tr from './Tr';
const Table = memo(({ tableData }) => {
return (
<table>
<tbody>
{tableData &&
tableData.map((v, i) => (
<Tr rowData={v} rowIndex={i}
key={`tr-${i}`} />
))
}
</tbody>
</table>
)
});
export default Table;
import React, { memo } from 'react';
import Td from './Td';
const Tr = memo(({ rowData, rowIndex }) => {
return (
<tr>
{rowData ?
rowData.map((v, i) => (
<Td tdData={v} tr={rowIndex} td={i}
key={`td-${i}`} />
))
: null
}
</tr>
)
});
export default Tr;
'React > webgame' 카테고리의 다른 글
slot machine (0) | 2024.10.17 |
---|---|
07 틱택토 (0) | 2024.04.16 |
06 로또번호뽑기 (0) | 2024.04.13 |
05 가위바위보 (0) | 2024.04.11 |
04 반응속도체크 코드 (0) | 2024.04.09 |