La foret rouge
Published on

6시간만에 CS 스터디에 사용할 룰렛 구현하기

Authors
  • avatar
    Name
    신주용

발단

저는 친구들과 CS(Computer Science) 스터디를 주기적으로 하고 있습니다. 이 스터디의 진행 방식은 다음과 같습니다.

  1. 스터디 당일 무작위로 선택된 1명이 20분 정도 공부 내용 정리 발표를 하고,
  2. 그 다음으로는 무작위로 질문자와 발표자를 뽑아 질의응답 연습을 합니다.
    • 예를 들어, A가 질문하면 C가 대답하고, B가 질문하면 A가 질문하는 식입니다.

그래서 매 스터디마다 무작위로 대상자들을 뽑아야 했습니다. 기존에는 네이버 원판돌리기와 사다리타기를 활용했습니다.

roulette
네이버 원판돌리기, 사다리타기

발표자 1명을 뽑는 경우는 원판돌리기로 아무 문제 없었습니다. 문제는 사다리타기로 질문자와 답변자를 뽑을 때였습니다. 사다리를 타면 때때로 아래처럼 질문자와 답변자가 동일한 경우가 발생합니다.

roulette
동일인이 나온 경우. 사다리를 다시 타야합니다.

그러면 사다리를 다시 타야되는데, 네이버 사다리타기는 이전 시도를 기억하는 기능이 없어 인원수와 이름을 처움부터 다시 적어줘야 했습니다. 다시 한 번 타서 잘 나오면 좋은데, 운이 나쁠 때는 대여섯 번씩 다시 돌려야 하는 경우도 있었습니다. 매 번 참여자 이름을 적어주는 것이 많이 번거로웠지만 네이버 사다리타기를 사용한다면 다른 방법이 없었습니다.

시도1: CLI로 실행하는 Python 스크립트

그러면 네이버 사다리타기가 아닌 다른 것을 쓰면 되지 않을까...? 필요하면 만들어 써야죠... 😂

첫 버전은 간단한 Python 스크립트였습니다.

"""
스터디 일시: Mon Dec  4 14:47:32 2023
참여 인원: ['김철수', '홍길동', '이영희']
오늘의 발표자: 김철수
질문 -> 답변
 김철수 -> 이영희 홍길동 -> 김철수 이영희 -> 홍길동
"""

with open("member.json", "r", encoding="utf-8") as fin:
    print(f"스터디 일시: {datetime.strftime(datetime.now(), '%c')}")

    members = json.load(fin)['members']
    print(f"참여 인원: {members}")

    # 오늘의 발표자 한 명 뽑기
    print(f"오늘의 발표자: {random.choice(members)}")

    # 질문 -> 답변
    members_shuffled = members.copy()
    while True:
        # 멤버 목록 섞고
        random.shuffle(members_shuffled)

        # 질문 == 답변 있는지 확인
        diff = [True for (m1, m2) in zip(members, members_shuffled) if m1 == m2]

        # 질문 == 답변 없다면 끝, 있다면 다시 섞기
        if len(diff) == 0:
            break

    print("질문 -> 답변")
    for idx, (m1, m2) in enumerate(zip(members, members_shuffled)):
        print(f"\t{m1} -> {m2}", end="\n" if idx % 3 == 2 else "")

정말 단순한 로직입니다.

  1. 참여자 이름 배열을 섞는다.
  2. 원본 배열과 섞은 배열의 원소를 순서대로 비교한다.
  3. 같은 원소가 없을 때까지 다시 섞는다.

그래서 이 스크립트를 작성하는 데도 그리 오랜 시간이 걸리지는 않았습니다.

cs study roulette

시도2: 웹페이지

그치만 터미널보다는 웹페이지가 더 예쁘지 않을까요..? URL만 있으면 접근 가능하고, 파이썬 가상환경을 만들지 않아도 되니까요. 그래서 Vue.js로 만들기 시작했습니다. 최근에 SSAFY 과정에서 Vue.js를 배웠는데, 예전에 쓴 리액트보다 더 쉬운 느낌이어서 금방 만들 수 있을거라 생각했습니다.

일단 미리 결과부터 보자면 다음 gif 파일과 같이 동작하고, 실제 사이트는 링크로 접근 가능합니다.

cs study roulette

기본적으로는 파이썬 스크립트를 웹으로 옮긴 것이기 때문에 로직에 큰 변화는 없습니다. 대신, 기존에는 참여자 정보를 JSON 파일로 입력받았는데 웹에서는 <input type="text"/>로 받고 있습니다. 때문에 김철수, 김영희, 홍길동과 같은 입력을 파싱하여 배열로 만들어 사용해야 됩니다. 사실 여기서는 (어차피 당장은 저희 스터디원만 쓸 것이므로) 간단하게 str.split(', ')을 사용하고 유저의 입력을 형식에 맞게 강제하는 방법을 쓰는게 제일 빠르지만, 이 방법 대신 저는 여기서 정규식을 사용하였습니다.

src/App.vue
<button class="btn btn-outline btn-accent btn-wide" @click="draw">Go!</button>

const draw = () => {
  // ...
  names.value = Array.from(names_input.value.matchAll(/[-]+/g)).map((e) => e[0])
  // ...
}

이렇게 구현하면 김철수,김영희, 홍길동과 같이 유저의 입력에서 공백 하나 정도 실수는 모두 ["김철수", "김영희", "홍길동"]으로 추출할 수 있으므로 유저도 더 편하게 사용할 수 있습니다.

그리고 발표자를 뽑는 룰렛이기 때문에 약간의 쫄깃함(?)을 추가하려고 Spinner도 하나 추가했습니다. 사실 로직 자체가 느릴 이유가 하나도 없어서 간단해서 실행하는 데 1초도 안 걸리지만, 결과를 보여주기 전 Spinner를 보여주면서 3초 sleep을 강제로 걸었습니다.

결과적으로 빌드를 하면 단 1페이지짜리 서비스이므로 3개의 파일만 생성됩니다.

.
├── assets
│   ├── index-F8XywCBR.css
│   └── index-hDG3tniO.js
└── index.html

GitHub Pages를 통한 배포

이제 웹페이지는 완성을 했으니 도메인으로 접근할 수 있도록 배포를 해야됩니다. 이번에 만든 사이트는 Vue.js를 이용한 정적 사이트이기 때문에 빠르고 간편한 GitHub Pages를 활용하였습니다.

그런데 여기서 약간 어려운 점이 있었습니다. 저는 기존에 제 블로그를 https://cheesecat47.github.io/ 주소로 GitHub Pages를 통해 배포중이었습니다. 그래서 이 룰렛 페이지는 repository 명을 하위 경로로 하는 https://cheesecat47.github.io/CS-Study/ 주소로 배포하려고 했습니다. 그런데 배포 후 접속해보니 페이지를 제대로 불러오지 못하는 문제가 있었습니다.

roulette
404 에러가 발생하며 js, css 파일을 불러오지 못합니다.

개발자 도구로 보면 jscss 파일을 불러오지 못해 Vue로 만든 웹사이트를 제대로 보여주지 못하는 문제입니다. 그런데 주소를 보니 https://cheesecat47.github.io/CS-Study/ 주소가 아니라 https://cheesecat47.github.io/ 아래에 있는 assets 디렉토리로 요청을 하는 것을 확인하였습니다.

빌드 된 dist/index.html 파일을 보면 다음과 같이 되어있습니다.

dist/index.html
<!-- ... -->
<title>cheesecat47's cs study roulette</title>
<script type="module" crossorigin src="/assets/index-hDG3tniO.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-F8XywCBR.css" />
<!-- ... -->

그러니 https://cheesecat47.github.io/CS-Study/assets/가 아니라 https://cheesecat47.github.io/assets/ 경로로 접근했던 것입니다.

이 문제는 빌드 시에 발생하는 것이므로 vite build 문서에서 해결 방법을 찾을 수 있습니다. package.jsonbuild 스크립트에 옵션을 추가하여 다음과 같이 빌드하면 됩니다.

package.json
{
  // ...
  "scripts": {
    "build": "vite build --base=/CS-Study",
    // ...
  },

그러면 빌드 된 dist/index.html 파일을 보면 다음과 같이 Public Base Path가 추가되어 있고, 웹페이지도 정상적으로 접근 가능하게 됩니다.

dist/index.html
<!-- ... -->
<title>cheesecat47's cs study roulette</title>
<script
  type="module"
  crossorigin
  src="/CS-Study/assets/index-hDG3tniO.js"
></script>
<link rel="stylesheet" crossorigin href="/CS-Study/assets/index-F8XywCBR.css" />
<!-- ... -->
cs study roulette

마무리

파이썬 스크립트는 기존에 작성해둔 것이고, Vue.js로 웹사이트를 만들 때부터 배포까지 총 6시간이 걸렸는데 (저녁 시간 제외) 한 페이지짜리 간단한 프로젝트여서 금방 끝낼 수 있었던 것 같습니다. GitHub Pages로 배포할 때 약간 어려움이 있었지만, 방법을 찾아 생각보다 금방 끝나서 다행이었습니다. 그리고 짧은 시간에 필요한 것을 금방 만들어 쓸 수 있을 정도로 성장해서 뿌듯한 기분도 든 토이프로젝트였습니다.