[로데시] 개발

[대학전쟁3] 양면빙고 웹 개발 03 - 타일 착수 (place) 본문

개인 개발/대학전쟁

[대학전쟁3] 양면빙고 웹 개발 03 - 타일 착수 (place)

로데시 2026. 1. 16. 19:36
반응형

 

0️⃣ 지난 번까지..

대학전쟁에 나온 '양면빙고 (double sided bingo)' 게임을 보고
 
이 게임을 웹으로 만들면 재밌겠다는 생각이 들어 양면빙고 웹 개발을 시작했다.
 

 
지난 글에서는 타일 list의 순서를 바꿀 수 있는 기능을 만들었다.
 
오늘은 이어서 본격적으로 빙고판을 만들고 빙고판에 착수 할 수 있는 기능을 구현해보고자 한다.
 

 

 

 

 

1️⃣ TileItem 정리

빙고판을 만들기 전에 앞서서, TileItem 코드를 정리하고자 한다.
 
이전에는 타일 하나를 담당하는 TileItem와 타일 전체를 담당하는 TileList 2개를 만들었다면
 
TileItem을 타일 이동을 담당하는 SortableTileItem과 타일 보여주는 것을 담당하는 TileItem으로 나누었다.
 

 
📌 SortableTileItem.js 코드
 
// 타일 내 이동 가능
const SortableTileItem = ({ tile, onFlip }) => {
  const {
    attributes, listeners, setNodeRef, transform, transition, isDragging,
  } = useSortable({
    id: tile.key,
    data: {
      type: "TILE",
      tile,
    },
  });

  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0 : 1,
  };

  return (
    <div ref={setNodeRef} style={style}>
      <div {...attributes} {...listeners}>
        <TileItem
          tileId = {tile.id}
          isFlipped = {tile.isFlipped}
          onClick = {onFlip}
        />
      </div>
    </div>
  );
};

export default SortableTileItem; 

 

 
📌 TileItem.js 코드
 
// 타일이 어떻게 보이는가
const TileItem = ({ tileId, isFlipped, onClick }) => {

  return (
    <div className="TileItem" onClick={onClick}>
      <img
        className="tile-img"
        src={getTileImgById(tileId)}
        alt=""
        draggable={false}
      />
      {isFlipped}
    </div>
  );
};

export default React.memo(TileItem); 

 

 

 

2️⃣ 빙고판 만들기

이제 이름만 '빙고'가 아닌 드디어 '빙고'라 부를 수 있는 빙고판을 만들 차례이다.
 
Bingo 칸을 담당하는 BingoCell과 Bingo 판을 담당하는 BingoBoard를 만들었다.
 

 
📌 BingoCell.js 코드
 
// 빙고 칸을 담당
const BingoCell = ({ id, tile }) => {
  const { setNodeRef } = useDroppable({
    id,
    data: { type: "BINGO_CELL" },
  });

  return (
    <div ref={setNodeRef} className="BingoCell">
      {tile && <TileItem tileId={tile.id} isFlipped />}
    </div>
  );
};

export default BingoCell; 

 

 
📌 BingoBoard.js 코드
 
// 빙고 판을 담당
const BingoBoard = ({ boardTiles }) => {
  const cells = Array.from({ length: 16 }, (_, i) => i);

  return (
    <div className="BingoBoard">
      {cells.map((cellId) => (
        <BingoCell
          key={cellId}
          id={cellId}
          tile={boardTiles[cellId]}
        />
      ))}
    </div>
  );
};

export default React.memo(BingoBoard); 

 

 
📌 빙고판 사진
 
양면빙고 빙고판
 
빙고판이 잘 생성되었다!
 

 

 

3️⃣ 착수

타일 list 내 이동 & 타일 list에서 빙고판으로 착수(place)는 모두 동일 동작이므로
 
TileList에 작성한 handleDragEnd, handleDragStart 등은 삭제하고 App.js에 합쳐준다.
 

 
📌 App.js 코드
 
const handleDragStart = (event) => {
  const { active } = event;
  const tile = tiles.find((t) => t.key === active.id);
  setActiveTile(tile);
  };

  // 이동 끝
  const handleDragEnd = (event) => {
  const { active, over } = event;

  // TileList 내부 이동
  if (over?.data?.current?.type === "TILE") {
    setTiles((prev) => {
      const oldIndex = prev.findIndex(t => t.key === active.id);
      const newIndex = prev.findIndex(t => t.key === over.id);
      return arrayMove(prev, oldIndex, newIndex);
    });
  }

  // BingoBoard 착수
  if (over?.data?.current?.type === "BINGO_CELL") {
    setBoardTiles((prev) => ({
      ...prev,
      [over.id]: active.data.current.tile,
    }));

    setTiles((prev) => prev.filter(t => t.key !== active.id));
  }

  setActiveTile(null);
}; 

 

 
📌 빙고판에 착수
 
양면빙고 이동 / 착수
 

 

 

4️⃣ 문제 & 해결

❓ 문제 - 이동중인 타일이 보이지 않음
<DndContext></DndContext>
<DragOverlay></DragOverlay>

 
TileList.js에 작성한 <DndContext>, <DragOverlay> tag를
 
App.js에 옮기면서 발생한 문제였다.
 

 
💡 해결 - App.js
 
양면빙고 이동 / 착수
 
<DndContext>
    <DragOverlay></DragOverlay>
</DndContext>

 
<DndContext>, <DragOverlay> tag를 연속해서 쓰는 것이 아니라
 
<DragOverlay> tag를 <DndContext> 내부에 작성해야 한다.
 

 

 
❓ 문제 - 타일 list에서 뒤집기 안 됨
 
양면빙고 뒤집기 안 됨
src={getTileImgById(tileId)}

 
열심히 클릭을 하고 있지만, 뒤집기가 안 된다.
 

 
💡 해결 - TileItem.js
 
양면빙고 뒤집기 됨
 
src={getTileImgById(isFlipped ? getPairedTileId(tileId) : tileId)}

 
initialTiles에 isFlipped 속성을 추가하고 t / f 에 맞게 타일 이미지를 보여준다.
 

 

 
❓ 문제 - 빙고판 착수 시, 타일이 뒤집혀서 올라감
 
양면빙고 빙고판에서 뒤집힌 상태로 올라감
{tile && <TileItem tileId={tile.id} isFlipped/>}

 
빙고판에 착수할 때 항상 뒷면으로 올라간다는 문제가 있다.
 

 
💡 해결 - BingoCell.js
 
양면빙고 빙고판 뒤집힘 오류 해결
 
{tile && <TileItem tileId={tile.id} isFlipped={tile.isFlipped} />}

 
아까 추가한 isFlipped 속성을 Bingo 칸에 props를 전달해주지 않아서 생긴 문제였다. BingoCell에도 isFlipped를 추가해 연결해준다.
 

 

 
❓ 문제 - 기존 타일을 덮어씌움
 
양면빙고 타일 위에 타일을 올리면 덮어씌워짐
 
빙고판에 이미 착수된 타일 위에 올리면 덮어씌워지는 문제가 있다.
 

 
💡 해결 - App.js
 
💡 App.js 코드
const handleDragEnd = (event) => {
  const { active, over } = event;
  setActiveTile(null);

  if (!over) return;

  const activeTile = tiles.find(t => t.key === active.id);
  if (!activeTile) return;

  // TileList 내부 이동
  if (over.data.current?.type === "TILE") {
    const oldIndex = tiles.findIndex(t => t.key === active.id);
    const newIndex = tiles.findIndex(t => t.key === over.id);

    if (oldIndex !== newIndex) {
      setTiles(prev => arrayMove(prev, oldIndex, newIndex));
    }
  }

  // BingoBoard 착수
  if (over.data.current?.type === "BINGO_CELL") {
    const cellId = over.id;

    // 이미 착수된 타일이 있다 -> 다시 타일 리스트로
    if (boardTiles[cellId]) {
      return;
    }

    // 빈칸일 때만 착수
    setBoardTiles(prev => {
      const next = [...prev];
      next[cellId] = activeTile;
      return next;
    });

    setTiles(prev => prev.filter(t => t.key !== active.id));
    return;
  }

  setActiveTile(null);
};

 
App.js에 작성한 handleDragEnd 함수를 빈칸일 때만 착수하도록 다시 작성해준다.
 
양면빙고 먹힘 오류 해결
 
이제 더 이상 타일이 먹히지 않는다!!
 
빙고판도 중앙에 위치시켰고 타일 list와의 간격(margin) 값도 부여해줬다.
 

 

 

 

 

5️⃣ 앞으로...

이제 양면빙고의 주기능 중 하나인 '착수(place)'를 끝냈다.
 
이제 양면빙고의 나머지 핵심 기능인 '이동, 뒤집기'를 구현할 것이다!
 

 

 

 

 

반응형