'샘' 이란 방탈출 은어를 아시나요?
방탈출 세계에서 '샘' 이란 은어가 있습니다.
샘은 아침샘, 밤샘을 뜻합니다.
방탈출 매장을 영업 외 시간인 이른 아침과, 밤(새벽)에 대관하여 모든 테마를 하는 걸 의미합니다.
주로 한 타임에 모든 테마에 입장할 수 있게, 테마에 맞게 인원을 구성합니다.
한 매장에 6개의 테마가 있다면, 3명씩 6팀으로 주최자 포함 총 18명이 함께 합니다.
샘을 하면 좋은 점은 단체로 입금하다보니 조금 더 할인을 받을 수 있다는 장점이 있습니다.
참고로 모든 매장이 대관이 가능하지는 않아요.
매장에 먼저 문의하여 해당 일정에 대관이 가능한지 확인해야 합니다.
방탈출 기록지가 있는 이유!?
샘을 가게 되면 꼭 한 분은 아래처럼 기록지를 출력해서 옵니다.
맨 윗 칸은 테마 사진과 테마명, 테마 난이도(코로리 방탈출에서 측정한 난이도 1~10)가 있습니다.
맨 왼쪽 같은 A팀부터 시작하는 팀들과 닉네임들이 있습니다.
그리고 각 팀별로 어떤 순서로 테마에 들어가고, 남은 시간은 얼만지, 힌트는 얼마나 썼는지 기록하게 됩니다.
방탈출 기록지를 사용하는 이유는 다음과 같아요.
- 잊지 않기 위해 빠른 시간 기록 : 짧은 시간 안에 많은 테마를 경험하기에 모든 테마가 끝난 다음에 일일이 초 단위까지 시간을 기억할 수 없습니다. 테마가 끝나면 바로 바로 기록이 가능해야 했습니다.
- 시간 예상 : 다른 팀들의 클리어 시간으로 대략적인 난이도와 탈출 시간이 예상이 됩니다.
- 추후 기록 : 개인적으로 앱이나 기타 도구로 자신만의 방탈출을 기록하는 분들이 계십니다. 샘을 하는 동안 정신이 없고 바쁘기에 여유롭게 기록할 수 있는 상황이 여의치 않습니다. 방탈출 기록지로 수치를 기록하고 나중에 여유로울 때 한 번에 정리하려는 목적도 있습니다.
방탈출 기록지의 문제
방탈출 기록지의 문제는 늘 누군가는 양식을 만들어야 하고 출력까지 해야한다는 문제가 있어요.
제가 보기엔 방탈출 기록지 양식은 거의 동일하여 IT 기술로 해결할 수 없을까, 하는 영감이 떠올랐습니다.
방탈출 기록지를 사용했을 때 필요하다고 느꼈던 점은 다음과 같아요.
- 현재 어떤 팀이 어떤 테마에 들어갔는지 한 눈에 알 수 없다.
- 어떤 팀이 몇 개 남았는지 한 눈에 알 수 없다.
- 늘 누군가가 출력을 해서 가지고 와야 한다.
온라인 방탈출 기록지 요구사항
온라인 방탈출 기록지 플랫폼 환경으로 프로그램, 모바일, 웹 환경이 떠올랐습니다.
다만 프로그램과 모바일은 특정 기기에 극한되어 있어 매 번 휴대하기 번거로울 것 같았어요.
따라서 웹 환경으로 제작하기로 결정했습니다.
지난 다른 밤샘에서 테스트하기 위해 하루가 다 가기 전에 제작을 해야 했어요.
웹 HTML, CSS, Javascript 으로 뚝딱뚝딱 만들기로 결심했습니다.
웹 프론트 개발은 익숙치 않아 React, NextJS 로 하루 만에 만들 자신이 없더라고요.
원조 프론트(?) 셋트로 가장 간단하게 개발을 했습니다.
기존 종이의 레이아웃을 웹페이지로 그대로 옮겼어요.
웹페이지로 만들면 Github Pages 으로 배포도 쉬웠습니다.
모든 데이터를 테이블 태그로 만들고, 모든 칸을 하드코딩해서 만들었어요.
요구사항은 다음과 같이 작성했어요.
- 순서 기록이 가능하다.
- 가장 최근 순서가 시간 기록이 안되어 있으면, 입장 중이라고 색깔로 보여준다.
- 완료한 테마는 색깔로 구분한다.
- 시간 기록이 가능하다.
- 시간 기록 시 시간과 힌트 수를 기록한다.
- 사진 촬영이 가능하다.
- 최근에 찍은 사진은 보여진다.
- 촬영한 사진은 로컬 폴더에 저장한다.
- 테마 제목과 남은 시간 기록이 함께 사진이 촬영된다.
- 좌우 반전이 된다.
팀별로 순서만 기록하면 현재 어떤 테마를 하고 있는지 주황색으로 표시해줘, 무슨 테마가 남았는지 알 수 있게 했어요.
순서와 남은 시간을 기록하면, 초록색 표시로 완료한 테마라고 표시했습니다.
테마를 마치면 인증샷도 남기고 싶어서 카메라 기능도 구글링으로 찾아봤어요.
생각보다 간단해서 인증샷 촬영 기능도 넣고, 시간이 남아서 남은시간과 힌트도 촬영할 때 HTML 캔버스에 기록하기로 했어요.
( 실제로 테스트 해보니 눈에 잘 띄지 않긴 했어요. )
로컬 스토리지로 모든 데이터 저장
페이지가 실수로 새로고침 하더라도 입력값들이 유지가 되었으면 좋을 것 같았어요.
서버가 따로 있는 게 아니다 보니 데이터를 로컬에 저장하고자 했어요.
Window 객체가 localStorage 를 갖고 있으니, 바로 localStorage 을 사용할 수 있었어요.
setItem(키, 값) 과 getItem(키) 으로 손쉽게 저장하고 불러올 수 있었어요.
function save() {
let inputs = document.querySelectorAll("input[type=text]");
inputs.forEach((input) => {
var id = input.id;
var value = input.value;
localStorage.setItem(id, value);
});
}
function load() {
let inputs = document.querySelectorAll("input[type=text]");
let index = 0;
inputs.forEach((input) => {
input.id = `input-text-${index}`;
var id = input.id;
var value = localStorage.getItem(id);
input.value = value;
focustOut(input);
index++;
});
}
로컬 스토리지는 언제까지 저장이 될까?
만약 로컬 스토리지에서 만료 기간이 있어, 모든 데이터가 갑자기 사라진다면 무척 황당스럽지 않을까 생각이 들었어요.
다행히 로컬 스토리지는 만료 기간이 없었습니다.
MDN 의 글을 인용했습니다.
localStorage is similar to sessionStorage, except that while localStorage data has no expiration time, sessionStorage data gets cleared when the page session ends — that is, when the page is closed. (localStorage data for a document loaded in a "private browsing" or "incognito" session is cleared when the last "private" tab is closed.)
보통 로컬 스토리지의 만료 시간을 정해주기 위해, 개발자가 직접 저장한 시각이 담긴 객체를 저장합니다.
매 번 저장한 시각을 불러와서 특정 시간동안 지났는지 확인하면, 로컬 스토리지의 값을 지워주는 로직입니다.
물론 그 로직이 담긴 페이지에 가지 않으면 계속해서 로컬 스토리지에 저장이 되어 있지만요.
여기서 sessionStorage 라는 것도 있는 걸 알게 됐어요.
위 설명에 따르면, 세션 스토리지의 데이터는 페이지 세션이 끝나면 모두 없어진다고 나옵니다.
그럼 페이지 세션은 또 무엇일까요?
세션 스토리지는 무엇일까?
세션 스토리지 MDN 글에서 페이지 세션은 다음과 같다고 설명합니다.
- 페이지 세션은 브라우저가 열려있는 한 새로고침과 페이지 복구를 거쳐도 남아있습니다.
- 페이지를 새로운 탭이나 창에서 열면, 세션 쿠키의 동작과는 다르게 최상위 브라우징 맥락의 값을 가진 새로운 세션을 생성합니다.
- 같은 URL을 다수의 탭/창에서 열면 각각의 탭/창에 대해 새로운 sessionStorage를 생성합니다.
- 탭/창을 닫으면 세션이 끝나고 sessionStorage 안의 객체를 초기화합니다.
(한국어)
- Whenever a document is loaded in a particular tab in the browser, a unique page session gets created and assigned to that particular tab. That page session is valid only for that particular tab.
- A page session lasts as long as the tab or the browser is open, and survives over page reloads and restores.
- Opening a page in a new tab or window creates a new session with the value of the top-level browsing context, which differs from how session cookies work.
- Opening multiple tabs/windows with the same URL creates sessionStorage for each tab/window.
- Duplicating a tab copies the tab's sessionStorage into the new tab.
- Closing a tab/window ends the session and clears objects in sessionStorage.
(영어)
새로고침과 페이지 복구를 거쳐도 세션 스토리지의 데이터가 남아있다고 합니다.
다수의 탭 또는 창을 열면 각각 세션 스토리지가 생성된다고 합니다.
탭과 창을 닫으면 비로소 세션 스토리지 객체를 초기화합니다.
제가 원했던 건 탭을 닫아도 데이터가 저장이 되어 있어야 하므로, 로컬 스토리지를 선택했습니다.
웹캠 촬영
웹캠 촬영은 구글링으로 찾았습니다.
navigator.mediaDevices
.getUserMedia({ video: true, audio: false })
.then((stream) => {
video.srcObject = stream;
video.play();
})
.catch((err) => {
console.error(`An error occurred: ${err}`);
});
navigator.mediaDevices.getUserMedia({video: true, audio: false}) 으로 웹브라우저에서 카메라 권한 허용을 물어보게 됩니다.
만약에 허용한다면 비디오 태그에 MediaStream 객체를 설정합니다.
촬영 시작을 누르면 아래와 같이 video 태그를 전체 화면으로 그리게 됩니다.
const context = canvas.getContext("2d");
...
context.drawImage(video, 0, 0, width, height);
...
const data = canvas.toDataURL("image/png");
photo.setAttribute("src", data);
...
drawImage MDN 에서도 HTMLVideoElement 를 인자로 넘겨 그려준다고 합니다.
아래는 첫 번째 인자에 대한 설명입니다.
- image
An element to draw into the context. The specification permits any canvas image source, specifically, an HTMLImageElement, an SVGImageElement, an HTMLVideoElement, an HTMLCanvasElement, an ImageBitmap, an OffscreenCanvas, or a VideoFrame.
이를 Canvas 에 toDataURL("image/png") 으로 만들어 이미지 태그에 설정합니다.
이후에 언제든 이미지를 다운로드 받을 수 있도록 합니다.
Canvas.toBlob(callback, type, quality) 으로 아래와 같이 링크를 생성하여 바로 다운로드 받을 수 있게끔 할 수 있습니다.
canvas.toBlob(
function (blob) {
var blobUrl = URL.createObjectURL(blob);
if (document.querySelector("a") == null) {
var link = document.createElement("a");
document.body.appendChild(link);
}
var link = document.querySelector("a");
link.href = blobUrl;
link.download = new Date() + ".png";
link.innerHTML = "Click here to download the file";
link.display = "none";
link.click();
},
"image/png",
1
);
시연 결과
제 아이패드를 직접 들고가 시연을 했어요!
다행히 사람들이 신문물(?) 이라며 신기해했어요.🫢
제 작은 어깨가 우뚝 올라갔습니다. 🤣🤣
아이패드 화면 전환 시 캔버스 사이즈가 달라져 사진 비율이 이상해지는 이슈와 촬영에 남은 기록 텍스트가 잘 안보이는 이슈가 있어 다음에 개선해야 겠구나 싶었습니다.
또 로컬에 저장해 누군가 아이패드를 들고와야 하는 문제가 있어, 서버와 연결해 누구나 온라인 기록지를 만들 수 있고, 휴대폰으로 QR 또는 링크로 접속하여 테마가 끝날 때 마다 휴대폰으로 확인할 수 있게 만들면 좋지 않을까 아이디어만 기록했습니다. ☺️
실제 화면
화면 사진은 다음과 같아요.
후기
위키의 개념을 만든 컴퓨터 프로그래머 워드 커닝햄이 이런 말을 했습니다.
"Write Small But Useful Programs Everyday Day"
작지만 유용한 프로그램을 매일 만들어봐라, 라는 명언에 작은 유용한 프로그램을 만들어봤습니다.
사람들 반응도 너무 좋았었고 더욱 발전을 시켜보면 어떤 변화가 올지 기대되는 경험이었어요. ☺️
'프로젝트 > 단기 프로젝트' 카테고리의 다른 글
[BetterDay] #1. 잡담 - 분리된 모듈에서 @DataJpaTest (1) | 2024.06.14 |
---|---|
[커리어리 디스코드 봇] RSS XML에서 유효하지 않은 문자 제거 - Invalid bytes in character encoding (1) | 2024.05.04 |
커리어리 디스코드 봇 제작기 (0) | 2024.04.17 |
[Node.js] 도메인 접속 오류 / Typescript Declare Global 오류 / EAI_AGAIN getaddrinfo 오류 해결 방법 (0) | 2024.01.25 |
[단기 프로젝트 #1] 에브리타임 청소기 구글 크롬 플러그인 토이 프로젝트 후기 (0) | 2022.03.09 |
댓글