text_sample.xlsx
0.02MB

 

아래와 같은 (숫자이지만 스페이스들이 엉켜서 숫자로 처리할 수 없는) 자료를 처리해야 하는 경우가 꼭 생긴다.

 

저 숫자들을 가지고 뭔가 해야겠는데, 문제는 저게 숫자로 보이지만 숫자 앞뒤에 스페이스(공백)이 뒤섞여 있어서, 엑셀에서는 텍스트(문자열)로 인식되는 자료다. 

인터넷에서 '엑셀에서 숫자에 있는 공백 없애기' 어쩌고 해서 검색해보면 이런 방법들이 나온다.

1. trim 함수를 써서 공백 제거 

2. substitue 함수를 써서 공백 제거

3. ctrl + H (찾아 바꾸기)로 공백 제거

 

결론적으로 위 방법들은 아니다. 위 방법들은 숫자에 공백이 규칙적으로 붙어 있을 때 쓸 수 있는 방법들이다. 숫자 앞 뒤에 이쁘게 한 칸 씩만 공백이 있거나, 일정하게 두 칸 씩 붙어 있다거나...  

하지만 실무에서 맞닥뜨리는 자료들이 그렇게 이쁘게 되어있을리가 없지. 

 

우리는 파워쿼리를 써서 손쉽게 공백을 없앨 수 있다. 파워쿼리에서 데이터를 선택하고, 데이터를 10진수로 바꿔주면 끝!

 

1. 먼저 내가 필요한 영역을 선택한다. 숫자 쪽만 선택해도 되지만, 나중에 더 편하려면 항목 부분까지 함께 선택하자.

 

2. 선택부분을 파워쿼리로 가져간다. 데이터 → 테이블/범위에서

 

3. 파워쿼리가 실행되면, 숫자 영역의 첫 열과 끝 열까지를 선택한다. 드래그는 안된다. shift키로 선택하자.

    선택하고, 변환 → 데이터 형식이 '임의'로 되어 있을텐데, '10진수'를 클릭. 

     이제 공백들은 모두 없어지고 숫자들로 변했다. 

 

4. 파일 → 닫기 및 로드 ,   엑셀로 돌아가면 작업 끝

 

5. 새로운 시트에 아래와 같은 데이터가 나타난다.

    모두 깔끔한 숫자들이다.  중간에 필요 없던 텍스트가 삭제되는 건 덤...

 

어렵지 않은 팁인데, 이걸 모르고 숫자들을 손으로 다시 입력하면........... 퇴근이 늦어진다.

 

예제파일 첨부합니다.

 

 

 

 

진짜 쓰는 실무 엑셀:유튜브 대표 엑셀 채널 오빠두가 알려 주는 엑셀 함수 보고서 작성 데이터

현재 별점 4.8점, 리뷰 1027개를 가진 진짜 쓰는 실무 엑셀:유튜브 대표 엑셀 채널 오빠두가 알려 주는 엑셀 함수 보고서 작성 데이터 분석 노하우!! 지금 쿠팡에서 더 저렴하고 다양한 MS Excel(엑셀)

www.coupang.com

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

나이를 먹고 몸이 망가지는 것을 느끼면서, '살기 위해서' 조깅을 시작한 지 한 3,4년 된 것 같다. 

그냥 별생각 없이 달렸는데, 최근 러닝 붐과 함께 주변 사람들이 기록을 들고 오는 걸 보고, 나도 기록 욕심 도발.

지난 5월 52분, 10월 50분.  ← 40분대 진입을 노렸는데, 너무 아쉬워서 한 달 만에 재도전.

 

47:32

 

이번에도 아쉬움은 있으나, 무릎이 버텨준 것에 감사하며, 이제 기록을 위한 러닝은 마쳐야겠다. 

힘들어서 표정이 찌그러져 있네... ㅎ

 

 

지난 경기에서 뒤쪽에서 뛰었다가 속도를 못 냈기 때문에, 앞쪽에서 시작했고,

45분 페이스 메이커를 쫓았는데.... 속았다.

난 페이스 메이커가 45분을 균등한 속도로 뛸 줄 알았는데, 초반에 슬슬 뛰다가 나중에 쭈욱 치고 나가더라. 시작할 때에는 내 평소 페이스대로 주욱 나갔어야 했는데, 페이스 메이커 만큼만 가면 될 줄 알았지. 6km까지는 쫓아갔으나, 그 이후엔 심장마비 걸릴까봐 포기!

 

5km 정도 지나면 같은 페이스의 사람들만 남는다.

옆에서 비슷하게 달리던 여성이 있었는데, 착지를 발 전체로 쿵쿵 소리가 날 정도로 뛰는거다. 저렇게 뛰면 오래 못갈거라는 안타까운 마음에 '그렇게 달리시면 안되고 발 앞 쪽으로 사뿐사뿐 뛰셔야 되요.'라고 조언하고 싶은 걸 꾹 참으면서 뛰었는데, 8km 이후에 난 헉헉대면서 걷고 그랬는데,  그 여성은 내 앞으로 저 멀리 주욱 나가더라. 

다시금 인생의 가르침을 깨달았다. 상대방이 조언을 구하기 전에는 네가 먼저 나서는거 아니다.  ㅡ,.ㅡ

 

속도 올리려고 나름 유튜브 영상들 보면서 흉내 좀 냈다. 인터벌,지속주,빌드업,하체 근력 운동... 

내 느낌에 속도 올리는 거에는 인터벌과 근력운동이 도움이 좀 더 된 것 같다.

무릎이 건강했으면 더 많이 훈련 할 수 있었는데 아쉽네.

 

뭘 알게 되면 새로운 세상이 열린다. 

러닝화도 그렇게 다양할 줄이야. 속 편하게 러닝 전문점 가서 추천하는 대로 샀다. 나름 직장인인데 좋은 거 사려고 카본화로 추천해달라 했더니, 가성비의 미즈노와 좀 더 비싼 뉴발란스를 추천하더라. 최근 유행으로는 뉴발란스를 골랐어야 하나, 소심하게 가성비로... 

러닝을 하면 발이 부어오르니까 두 치수 더 큰 사이즈로 고르라고 하는데, 내 생각엔 하프 이상 뛸 거 아니면 한 치수 큰 정도로 적당할 거 같다. 난 두 치수 크니까 발등 공간이 남는 느낌이라....

 

Mizuno , Neo Vista

 

러닝을 시작하고 보니까, 이전에 골프에 시간,돈 날리면서도 발전이 없어서 스트레스 받았던 것이 다 부질없다. 

요새 골프 필드 한 번 나갔다 올 돈이면, 러닝화 상위급을 살 수 있잖아!!!

 

스트레스 받지 말고, 건강하게 살자...

 

 

 

미즈노 네오 비스타 남자 여자 러닝화 마라톤화 블랙 핑크 옐로우 J1GC243403 - 러닝화 | 쿠팡

쿠팡에서 0.0 구매하고 더 많은 혜택을 받으세요! 지금 할인중인 다른 0 제품도 바로 쿠팡에서 확인할 수 있습니다.

www.coupang.com

 

 

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받을 수 있습니다.

뭐 홍콩이야 워낙 많이들 가고 추천 코스가 잘 알려져 있으니, 그런 건 제쳐놓고, 어느 여행가이드에도 없는 로컬 식당 추천한다.

이 식당의 가장 큰 장점은, 식당에서 운영하는 무료 셔틀 보트를 타고 가석 식사를 하고 다시 보트를 타고 돌아오는 점이다. 편도 30분 이상의 보트를 타면서 센트럴의 앞바다 및 홍콩섬 뒤 쪽의 풍경까지 감상할 수 있다.

보통 돈을 내고 유람선을 타서 홍콩 앞바다를 한바퀴 돌고 오는건데, 이건 무료!!

 

식당 이름은, Lamma Rainbow  (https://lammarainbow.com/?lang=en)

 

센트럴에서 배를 타면 아래의 코스로 가는건데 짧지 않은 거리다.

홍콩 앞바다에 유람선 타는 것보다는 훨 낫다고 생각하고, 

17:20 배를 타면, 갈 때의 석양 무렵 풍경과, 식사 후 올 때 야경을 모두 즐길 수 있다.

 

이런 2층 배를 타고 간다. 2층의 뒷쪽 자리를 추천한다. 앞 자리는 조종실 때문에 시야가 가린다. ㅎ

 

가면서 홍콩섬 남쪽의 풍경을 볼 수 있다. 이거 일반 여행자들은 절대 못본다. 굳이 왜 봐야하냐고 하면... 뭐 안봐도 되지.... 흠....

 

식당은 해산물 식당이니, 거기서 우리나라 횟집처럼 해산물을 고르고 요리를 시키면 되는데, 

우리나라에서도 어려운 개별 주문을 하기는 좀 거시기하고...

 

나는 3~4인 셋트 메뉴를 시키고 개별 요리를 추가 했다. 사진도 있고, 한글 메뉴도 있다!! (한국인 많이 오나... 난 4번인가 가면서 한국인 한 번도 못 봤는데...)

 

3~4인 세트 메뉴

 


 

 

맛?  와이프가 별 말 없었으면 평타 이상은 되는거다.

종업원들 친절하다.

 

이렇게 바다를 보면서 식사... 

 

주윤발 형님을 비롯한 유명인들 인증샷

 

이제 다시 앞으로 가서, 배를 어디서 타야하는 중요한 문제가 있다. 

 

시간표. 

 

저 시간대에 센트럴,침사츄이의 부두에 있다가, 위 사진의 배가 오면 타면 된다. 

탈 때, '나는 너희 식당에 가서 밥을 먹을거다'라는 표현을 확실하게 해야 한다. 아니면 영어 못하는 선원 아저씨가 그냥 배 얻어타고 가려는 놈인줄 알고 저리 가라고 한다. ㅎ 

센트럴의 경우, 9번 부두의 빨간색 부근에서 기다리면 된다.  (그런데, 배가 정말 오는건가 아닌가 되게 불안하다 ㅎ)

 

식당에서 돌아오는 건, 종업원이 미리 잘 알려준다.

17:00 배를 타고 갔으면, 19:20 배로 돌아오는게 딱 맞다. 그 이상 있어봐야 할 것도 없고... 

 

행운을 빈다~~

 

 

 

에이든 홍콩 여행지도(2024-2025):수만 시간 노력해 지도로 만든 홍콩 여행 가이드 총정리 - 국내도

쿠팡에서 에이든 홍콩 여행지도(2024-2025):수만 시간 노력해 지도로 만든 홍콩 여행 가이드 총정리 구매하고 더 많은 혜택을 받으세요! 지금 할인중인 다른 국내도서 제품도 바로 쿠팡에서 확인할

www.coupang.com

 

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

 

가장 먼저 내가 헤드셋을 사용하는 목적은, 업무할 때 통화용이고, 운동할 때 유튜브 청취용이다. 음악 감상 목적은 아니기 때문에 음질에 대한 깊은 비교는 없다. 

 

업무할 때 통화를 하면서 컴퓨터를 확인해야할 경우가 많기 때문에, 꼭 그렇지 않더라도 두 손을 자유롭게 해주는 헤드셋은 업무 효율성을 높여준다고 생각한다. 

 

일반적인  블루투스 이어폰을 쓰다가 항상 휴대해야 하는 불편함 때문에, Huawei 토크밴드B6를 알게되고 이것을 정말 잘써왔다. (이건 지금도 강추이고, 후속 모델이 없는게 너무 아쉽다.)

https://mrkool.tistory.com/56

 

Huawei 토크밴드B6 , 한 달 사용기

한 줄 평 : 이게 바로 실용적인 스마트 기기다!! 조깅할 때 거리측정의 목적으로 헬스밴드를 찾게 된다. 샤오미 미밴드의 엄청난 가격에 놀라서 구매를 했으나, 아... 싼 이유가 있구나.... 미밴드

mrkool.tistory.com

별도로 운동할 때에는 일반적인 블루투스 이어폰을 썼는데, 어느날 골전도 이어폰이라는 것을 알게되고 급 뽐뿌를 받는다.

더욱이 최근에는 온라인 회의가 많이 생겨서 헤드셋이 더욱 필요해졌는데, 귀를 막지 않아서 다른 소리도 들을 수 있다는 점이 매력적이라고 생각했다.

그래서 급기야 세 종류의 골전도 이어폰을 주르륵 사버리게 된다. 

그래서, 오늘 비교할 제품은, 아남 NO9, 로이체 BCE-300, 피스넷 Always이다.

아남 NO9은 방출해서 사진에 없다.

 

일단 골전도 이어폰의 장점은, 귀를 틀어막지 않기 때문에, 외부의 소리를 들을 수 있고, 귀를 막고 있는 답답함이 없다는 점이다.

그래서, 운동용으로 1차 아남 NO9을 샀다가, 이왕 산거 업무용으로도 쓰려고 통화음질을 찾으려고 BCE-300을 거쳐서 피스넷 Always에 멈추게 된다.

 

>> 아남 NO9   (구매가 \50,900)

골전도 이어폰을 찾다가 '아남'이란 브랜드를 보고 별 고민 없이 바로 질렀다. 가격이 부담 없기도 하지만, 국내 대표 음향기기 제조사를 응원하고 싶은 마음이 컸다. 

그러나, 바로 실망해버린다. 전원을 켤 때 '전원이 켜졌습니다'라는 여자 음성이 귀가 아플 정도로 우렁차다. 짜증이 날 정도... 개발자가 써보지도 않고 출시를 했나...

음질이야 소리만 나면 됬다고 생각하는데, 방출의 결정적인 이유는, 통화용으로는 절대 쓸 수가 없었다. 

목 뒤로 돌아가는 기구물이 좀 딱딱하다 싶고, 그 힘으로 귀 주변을 '찝어준다'는 느낌으로 귀에 장착되서, 좀 오래 쓰고 있으면 편하지는 않다.

 

>> 로이체 BCE-300 (구매가 \99,000)

앞서 역시 싼 건 안되겠구나 하고, 좀 더 비싼 제품, 통화음질이 괜찮다는 평을 보고 구매했다.

목 뒤로 돌아가는 기구물이 좀 더 부드럽고, 그래서 귀 주변을 찝는다는 느낌보다는 귓바퀴에 얺혀진다는 느낌이어서 착용감은 더 좋다.

통화 음질도 더 좋아졌으나, 역시 상대방으로부터 잘 안들린다는 불만이 생겼다. 역시 마이크 한 개로는 안되는건가... (그런데, 화에이 토크밴드는 통화 품질도 좋았는데...)

 

>> 피스넷 Always (구매가 \89,000)

통화를 하려면 좀 더 비싼 제품으로 가거나 아예 전용 마이크가 있는 제품으로 가야하나 하다가, 

통화품질을 전면에 내세우는 제품 설명을 보고 선택했다. 그리고 이 제품은, 골전도 방식은 아니고, '오픈형 이어폰'이라고 하는데, 느낌으로 말하자면, 귀 입구에 작은 스피커를 설치한 느낌?

음질은 충분히 좋다. 그리고 통화 음질도 합격점이다. (음질 비교는 아래에서...)

귀에 각각 착용되니까, 일체형 골전도 제품의 와이어가 걸리적 거리는 문제는 없다. 그렇다고 물리적으로 부딪치지 않는 이상, 운동하다가 벗겨질 것 같지도 않다.

 

아남 NO9 통화음
로이체 BCE-300 통화음
피스넷 Always 통화음

 

주파집 TWS 통화음

 

 

 

동일한 음량으로 말했는데, 차이가 확 난다. (너무 차이가 있어서, 피스넷의 제품이 볼륨을 더 크게 설정됬나 하는 의심은 있다.)

현재까지 며칠 업무상 통화에 사용했는데, 상대방으로부터 큰 컴플레인은 없었다. 그래도 역시 폰으로 직접 통화하는 것만큼은 못한 거 같다.

그래 이정도면 됬지... 하고 만족하려는데, 큰 결점이 발견됬다. 멀티페어링이 안된다!!  (앞선 두 제품은 멀티 페어링이 되는데, 가장 최근 출시된 제품이 안되다니?!!??!) 원래 생각했던 건, 노트북과 휴대폰에 멀티페어링을 해놓고, 온라인회의와 전화통화를 동시에 해결하려는 생각이었는데...  피스넷 제품은 1:1 연결만 지원한다.  별 수 없이, 이 부분은 포기해야지. 여기에서 제품을 하나 더 살 수는 없고...

 

앞선 세 제품을 구매하는데 총25만원이 들었다. 이렇게 될 걸, 애초에 가장 유명한 Shokz 제품을 샀으면 한 방에 해결됬을걸 하는 생각이 든다. 그래도 나는 2등을 응원한다는 소심한 방향성은 있다.

 

최종적으로 로이체 제품은 집에서 운동용으로 사용하고, 피스넷 제품은 회사에서 업무용으로 사용하게 됬다.

 

로이체 APT X HD탑재 멀티페어링 방수 방진 골전도 블루투스 무선 이어폰, BCE-300, 블랙

 

강력한 통화품질 핸즈프리 오픈형 무선 블루투스 이어폰 피스넷 올웨이즈 / 골전도 대체, 블루투스이어폰 피스넷 올웨이즈

 

 

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

 

엑셀 실무에서 vlookup은 기본중에 기본인데 (요새는 xlookup으로 사용하자)

가끔씩 2개 이상의 조건에 맞는 값을 가져와야 할 때가 있다. 아래의 경우에서는, code, part number, customer, 이 3개의 조건에 맞는 값을 오른쪽 녹색 표에 가져오는 것이겠다.

뭐 사실 vlookup/xlookup으로도 해결할 수는 있다. 조건 열들을 하나로 합쳐서 그 합친 셀로 vlookup을 하면 되는데...

보다시피, 열이 늘어나고, 없는 값은 #n/a가 표시된다.

 

그러나, 파워피벗을 사용하게 되면, 함수 사용 없이 깔끔한 표가 완성된다.

과정은,

] 각각의 테이블을 '표'로 변환한다. 단축키 ctrl + t

] 먼저 가져올 자료인 녹색 테이블 위에서 '데이터' > '테이블 범위에서'를 눌러서  '파워쿼리'를 실행한다.

] 여기서는 할 일 없고, '파일' 탭을 눌러서 다시 닫아주는데, '닫기 및 다음으로 로드...'를 누르고, 다음 화면에서 '연결만 만들기'를 선택한다. 

  그냥 '닫기 및 로드'를 선택하면 별도의 시트가 생성되는데, 그럴 필요가 없으니...

] 이제 같은 방법으로, 기준이 되는 자료(파란색 표)에서 파워쿼리를 실행한다. ( '데이터' > '테이블 범위에서')

] 메뉴 중간에 있는 '쿼리 병합'을 누른다. 팝업 창에서 상단에는 현재의 표가 있고, 아래쪽에서는 아까 만들었던 표를 선택해서 가져온다.

  여기서 조건이 될 열들을 위 아래에서 각각 선택해주면 된다. 

  중간에 '조인 종류'라고 선택이 있는데, 기본 값이 우리가 vlookup에서 알고 있던 조건이므로 손대지 않는다.

] 이제 가져오려던 표가 몽땅 불러들여와졌다. 여기서 필요한 열만 선택한다. 나는 Revenue 2개 값만 선택했다. 

] 여기서도 조건에 맞는 값이 없던 셀들은 null이 나오는데, 상단의 '값 바꾸기'에서 0으로 바꿔준다. (이렇게 바꿔주면, 향후에 데이타가 추가되서 또 다른 null의 경우가 생기더라도 0으로 바꿔주는 놀라운 기능이다.)

] 이제 '닫기 및 로드'를 눌러주면 새로운 시트에 깔끔한 표가 완성된다.

  이후에 데이터가 추가되더라도, '모두 새로 고침' 버튼 한 번에 자료가 수정되는 행복을 느낄 수 있다.

 

 

 

 

한빛미디어 엑셀 피벗 파워 쿼리 바이블 - 엑셀 피벗과 파워 쿼리를 다루는 거의 모든 기능

COUPANG

www.coupang.com

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

제목으로 말이 이상한데... 이미 피벗테이블을 거쳐서 나온 데이터를 원래의 데이터로 돌린다는 말이다.

엑셀의 정식 표현은 '열 피벗 해제'이다.

아래와 같이 월별로 정리된 데이타를 다시 원래의 데이터로 돌린다는 의미이다.

 

원래 데이터는 애초에 오른쪽과 같이 만들어야 여러가지로 활용이 가능할텐데, 이미 왼쪽처럼 작성된 자료를 오른쪽처럼 회복(?) 시켜야 할 때, 엑셀의 '파워쿼리'가 손쉽게 해결해준다. 

 

] 먼저 원래의 데이터를 '표'로 지정해준다.  단축키 ctrl + t

] '데이터' > '테이블 범위에서'를 누르면  '파워쿼리'가 실행된다.

] 데이터의 중간중간에 null 값이 보이는데, 1월부터 6월까지 데이터 열을 전체 선택하고,  '홈' >'값 바꾸기'에서 null을 0으로 바꿔주자.  엑셀에서 '찾아 바꾸기'와 동일한 기능.

  null 값이면, 행을 열로 바꿀 때, null은 열로 바꾸지 않고 누락시킨다.

] 이제 null은 0으로 바꼈고, 아직 1월~6월이 선택되어 있을텐데, 이 부분이 행으로 바꾸고 싶은 부분이다.

 선택한 상태 그대로 두고, '변환'>'열 피벗 해제'를 누르면 우리가 기대했던 결과가 나온다.

] 이제 '홈'>'닫기 및 로드'를 선택하면, 별도의 시트에 결과 값을 얻을 수 있다. 

 

막상 결과를 얻고 나면, 별거 아니게 느껴지지만, 이게 실무에서 닥치면 상당히 난감한 일이다. 일일이 한 행 한 행에다가 개월 수 만큼 열을 삽입해서 값을 넣어준다??  말도 안되는 일이다.  (물론 애초에 데이터 형태를 잘 맞추면 일어나지 않을 일이다. ㅎ)

 

파워쿼리의 강력한 점은, 한 번 이렇게 작업을 정의해 놓으면 데이터가 추가되더라도 간단하게 처리된다는 점이다.

아래와 같이 추가 데이터를 붙여넣어도, '모두 새로 고침' 버튼 한 번에 자료가 수정된다.

9백만이었던 숫자가 데이터를 추가하고 10백만으로 고쳐지는 걸 볼 수 있다.

이번에는 자료에 수량만 있었지만, 수량과 금액이 있는 경우에는 어떻게할지 다음 포스팅에서...

 

 

엑셀 피벗&파워 쿼리 바이블:엑셀 피벗과 파워 쿼리를 다루는 거의 모든 기능

COUPANG

www.coupang.com

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

blender... 알면 알수록 굉장한 프로그램이다. 무료라서 더더욱 놀랍다.

 

충돌,낙하,유체의 흐름 등을 시뮬레이션 할 수 있다.

라이언이 쇠줄에 매달려서 블럭에 충돌하는 영상을 만들어봤다.

원래 내가 본 동영상은 동그란 원형물체이지만, 나는 라이언으로 바꿔봤다. 

이 정도의 영상을 10분이면 만들 수 있다는...  @_@

 

시뮬레이션에서는 각 큐브 한 개씩이 1kg이고, 쇠사슬의 사슬 하나의 무게는 4kg, 라이언의 무게는 자그마치 150kg이다. 이런 무게들까지 감안해서 충돌이 일어나고, 매달린 라이언의 무게가 너무 지나치면 쇠사슬이 끊어진다. 놀라운 시뮬레이션....

 

만들 때마다 채색이 아쉽다. 물체의 재질과 조명을 잘 설정해야하는데, 여기서 전문가와의 차이가 생긴다. 스킬은 비슷하게 쫓아할 수 있지만, 마지막의 차이를 만드는 감성의 부분은 쫓아가기 힘들다.

 

 

 

웨이드 초고속 진공 블렌더 믹서기

COUPANG

www.coupang.com

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받을 수 있습니다.

앞서 만들었던 오목에 자신감을 얻고 다음 프로젝트로 리버시를 만들기로 한다. 

 

Javascript 오목 게임 만들기

오목 게임. 아이들이랑 놀 겸해서 만들어봤다. 완성본은 아래에서... omog-geim.bryanko555.repl.co HTML,CSS는 별거 없고, 게임승리 이미지를 안보이게 넣어뒀다가 오목이 만들어졌을 때 나타나게 했다. 큰

mrkool.tistory.com

리버시 게임은, 돌을 놓아서 자기편의 돌 사이에 상대편의 돌이 끼어 있는 형태를 만들게 되면 따먹을 수 있는데, 단, 따먹은 돌은 없어지는 게 아니라 자기편의 돌로 변한 채로 그 자리에 있게 된다. 즉, 상대편의 돌을 뒤집어 자기편의 돌로 만드는 것이다.

완성된 게임은 아래에서...

https://sad-jennings-0ef849.netlify.app/

 

HTML,CSS는 별거 없고, 게임승리 이미지를 안보이게 넣어뒀다가 오목이 만들어졌을 때 나타나게 했다.

이번에는 P5.js를 사용했기 때문에, P5.js파일을 읽어와야 된다.

 

처음 시작할 때는 오목보다 쉽게 만들 수 있을 줄 알았다. 그런데, 두 배는 더 어렵게, 시간을 더 들여서 만들었다.

오목에서보다 더 고려해야 할 점이,

] 돌을 놓을 수 있는 곳이 제한되어 있다. 오목처럼 아무곳에나 둘 수 있는 것이 아니어서, 둘 수 있는 칸을 매번 계산해서 보여줘야 된다. (검정색,하얀색 조그만 사각형)

] 게임 막판 즈음에는 돌을 놓을 수 없는 경우가 발생하고, 그렇게되면 1차는 상대방에게 순서를 넘겨야되고, 그 다음에도 놓을 곳이 없으면 게임 종료 시킨다.

] 오목은 돌의 색깔이 정해지면 계속 그대로이지만, 리버시는 매번 색깔이 바뀐다.

] 마지막에 자기 돌의 수가 많은 쪽이 이기는 게임이라, 돌의 수를 매번 세어서 보여줘야 한다.

 

오목에서처럼 각 칸을 숫자의 배열로 구성시키고, 거기서 연산을 해나간다. 

아래와 같이 빈 칸은 -1, 흰색 돌 0, 검정색 돌 1, 흰색이 둘 수 있는 곳 2, 검정색이 둘 수 있는 곳 3으로 숫자 배열을 계속 바꿔나가고, 그 배열에 맞춰 매 번 캔버스에 그림을 그려준다.

돌을 놓을 수 있는 곳은, 돌을 잡을 수 있는 곳에만 놓을 수 있는데, 이 경우를 찾는 곳에서 가장 애를 먹었던 것 같다.

매 번 각각의 칸에서 8방향을 모두 체크하고, 나와 다른 색깔의 돌이 있으면, 그 방향으로 다음 돌을 확인해나가다가, 그 끝에 나와 동일한 색의 돌이 있는 경우가 돌을 놓을 수 있는 경우다. 

 

돌이 놓아지는 경우에도 그 돌의 8방향을 모두 확인해서 잡은 돌의 색을 바꿔준다. (배열의 숫자 0을 1로, 아니면 1을 0으로...)

 

아래에 Javascript 파일 전문.  P5.js를 사용했지만, 그림 그리는 것만 좀 편해진 것이고, javascript의 기본 원리는 동일하다.

 

 

모던 자바스크립트 Deep Dive:자바스크립트의 기본 개념과 동작 원리

COUPANG

www.coupang.com

function setup(){
  canvasWidth = 600;
  canvasHeight = 600;
  cvs= createCanvas(canvasWidth,canvasHeight);
  background('#1b5e20');
    
  row = 8;
  rowWidth = canvasWidth / row;
  radius = rowWidth-20;
  count = 1// 돌을 놓는 차례. 1을 검정색으로 설정
  blackScore = whiteScore = 2
  blackAvailable = whiteAvailable = 2
  pass = 0;
  checkDirection = [[1-1], [10], [11], [01], [-11], [-10], [-1-1], [0-1],];
  board = new Array();
  buffer = new Array();
  boardHistory = new Array();
  tik = new Audio("tik.mp3");
  beep = new Audio("beep.wav");
  ending = new Audio("ending.wav");
  plz = new Audio("plz.m4a");
  oneMore = new Audio("oneMore.m4a");
 
  winScreen = document.querySelector('.winShow');
  turnPass = document.querySelector(".turnPass");
  // 보드 격자를 그리고, 모든 칸에 -1을 입력
  boardInit();
 
  // 시작 돌 4개 
  board[3][3= board[4][4= 1;
  board[4][3= board[3][4= 0;
  
  checkAvailable();
  drawBoard();
  
  document.querySelector("#black").innerHTML = "검정색 점수 : " + blackScore;
  document.querySelector("#white").innerHTML = "하얀색 점수 : " + whiteScore;
  button1 = document.querySelector("#withdraw");
  button2 = document.querySelector("#reload");
  troImg = document.querySelector("#trophyImg");
  
  // 무르기 버튼 동작
  button1.addEventListener('mouseup', ()=>{
    plz.currentTime = 0.5;
    plz.play();
    withdraw();
  })
  button2.addEventListener('mouseup', ()=>{
    oneMore.currentTime = 0.5;
    oneMore.play();
    setTimeout(() => {
      location.reload();
    }, 2000);
  })
 
  canvasPosition = cvs.position(0,30);
}
 
function boardInit(){
  // 보드 격자를 그리고, 모든 칸에 -1을 입력
  stroke("black");
  strokeWeight(2);
  fill('#1b5e20');
  for (i = 0; i < 8; i++) {
    board[i] = new Array(8);
    for (j = 0; j < 8; j++) {
      rect(i * rowWidth, j * rowWidth, rowWidth, rowWidth);
      board[i][j] = -1;
    }
  }
  console.log('-1 초기화')
   fill("black");
   circle(rowWidth * 2, rowWidth * 210);
   circle(rowWidth * 2, rowWidth * 610);
   circle(rowWidth * 6, rowWidth * 210);
   circle(rowWidth * 6, rowWidth * 610);
}
 
// 마우스 클릭하면 그 칸의 위치 파악
document.addEventListener("mouseup", (e) => {
  // console.log('offset' , e.offsetX, e.offsetY);
  // console.log('page' , e.pageX, e.pageY);
  
  if(e.offsetX > 0 && e.offsetX < canvasWidth && e.pageY > canvasPosition.y && e.offsetY > 0 &&  e.offsetY < canvasHeight){
    
  let rowX = floor(e.offsetX / rowWidth);
  let rowY = floor(e.offsetY / rowWidth);
  
  main(rowX, rowY);
  }
});
 
// 객체 복사하는데, 이전 값을 계속 참조하는 것을 막는 함수
function cloneObj(obj){
  const result = [];
  // console.log(obj)
  for( i = 0 ; i< 8 ;i++ ){
    result[i] = JSON.parse(JSON.stringify(obj[i]));
  }
  return result;
}
// 메인 함수. 클릭된 칸에 흰색(0), 검은색(1) 입력하고, 
// 뒤집어야 하는 함수 호출, 보드 전체 그리는 함수 호출
function main(x, y) {
 
  // 돌을 놓을 수 있는 곳일때만 주요 함수들 실행
  if (board[y][x] == 2 || board[y][x] == 3) {
    tik.currentTime = 0.5;
    tik.play();
    board[y][x] = count % 2;
    // 돌을 놓을 수 있는 위치에 3(검정)이나 2(흰색)로 보드에 입력하는 함수 호출
    checkReversible(x, y);
    count++;
    // 돌을 뒤집어야 하는 경우에, 바뀐 색으로 보드에 입력
    checkAvailable();
    let boardTemp = board;
    // board배열상태로 보드를 그려줌 
    drawBoard();
    // 검정,하얀 돌  개수 계산
    scoreCalc();
    // 무르기를 위해서 매 차례의 보드 배열을 통째로 boardHistory배열에 입력
     boardHistory.push(cloneObj(board));
 
 
 
    // console.log('boardHistory', boardHistory);
  } else {
    beep.play();
    console.log('Not Here')
  }
}
 
// 무르기 함수
function withdraw(){
  boardInit();
  boardHistory.pop();
  board = cloneObj(boardHistory.slice(-1)[0]);
  // board = boardHistory.slice(-1)[0];
  console.log(boardHistory);
  count--;
  drawBoard();
  scoreCalc();
}
 
// 점수 계산 함수
// 점수 계산 및 돌을 둘 수 있는 곳이 몇 개 있는지도 확인
// 만약 돌을 둘 수 있는 곳이 없으면, 나중에 패스하도록
function scoreCalc(){
  blackScore = whiteScore = 0;
  blackAvailable = whiteAvailable = 0
  for(i=0;i<8;i++){
    blackScore += board[i].filter((e) => e === 1).length;
    whiteScore += board[i].filter((e) => e === 0).length;
    blackAvailable += board[i].filter((e) => e === 3).length;
    whiteAvailable += board[i].filter((e) => e === 2).length;
  }
  document.querySelector("#black").innerHTML = "검정색 점수 : " + blackScore;
  document.querySelector("#white").innerHTML = "하얀색 점수 : " + whiteScore;
  
  if (count % 2 == 0 && whiteAvailable == 0 && pass != 2) {
    console.log("no white move");
    pass++;
  } 
  if (count % 2 == 1 && blackAvailable == 0 && pass != 2) {
    console.log("no black move");
    pass++;
  }
 
  // 놓을 곳이 없으면 상대방에게 차례 넘김
  if(pass == 1){
    console.log("다음 차례로 넘어갑니다.");
    turnPass.style.zIndex = 3;
    turnPass.style.visibility = "visible";
    turnPass.style.animationName = "moveDown";
    console.log(turnPass);
    setTimeout(() => {  
      turnPass.style.zIndex = -1;
    }, 2500);    
    count++;
  }
 
  // 혹시 둘 곳이 없어서 다음 차례로 넘어간 경우 때문에
  // 가능한 곳을 다시 찾고, 다시 그려주는 함수 호출
   pass++;
   checkAvailable();
   drawBoard();
 }
 
// 배열 상태대로 보드 전체에 돌 및 놓을 수 있는 위치를 그려줌
function drawBoard(){
   noStroke();
  
  // board배열에 따라 흰색,검정색,작은 사각형 그려주고
  for (i = 0; i < 8; i++) {
    for (j = 0; j < 8; j++) {
      switch (board[i][j]) {
        case 0:
          fill("white");
          circle((j+1)*rowWidth - floor(rowWidth/2), (i+1)*rowWidth - floor(rowWidth/2), radius);
          break;
        case 1:
          fill("black");
          circle((j+1)*rowWidth - floor(rowWidth/2), (i+1)*rowWidth - floor(rowWidth/2), radius);
          break;
        case 2:
          fill("rgba(255,255,255,1)");
          rect((j+1)*rowWidth - floor(rowWidth/2)-4, (i+1)*rowWidth - floor(rowWidth/2)-410,  10  );
          break;
        case 3:
          fill("rgba(0,0,0,1)");
          rect((j+1)*rowWidth - floor(rowWidth/2)-4, (i+1)*rowWidth - floor(rowWidth/2)-410,  10  );
          break;
        default : 
          fill("#1b5e20");
          rect((j+1)*rowWidth - floor(rowWidth/2)-8, (i+1)*rowWidth - floor(rowWidth/2)-816,  16  );
          break;
        }
    }
  }
}
 
// 원을 뒤집어야 하는지 여부 확인해서 0(하얀색),1(검정색)을 좌표에 입력
function checkReversible(x,y){
  // 8방향으로 체크하는 루프
  for (j = 0; j < checkDirection.length; j++) {
    cX = x;
    cY = y;
    while (cX >= 0 && cY >= 0 && cX < 8 && cY < 8) {
      cX = cX + checkDirection[j][0];
      cY = cY + checkDirection[j][1];
      if (cX < 0 || cY < 0 || cX >= 8 || cY >= 8) {
        break;
      } else {
          if (board[cY][cX] == count%2 || board[cY][cX] == -1 || board[cY][cX]  == 2 || board[cY][cX] == 3) {
            buffer.push([cX, cY]);
            break;
          } else {
            buffer.push([cX, cY]);
          }
      }
    }
    // console.log(buffer)
    if (buffer.length >= 1) {
      if (
        count%2 == board[buffer[buffer.length - 1][1]][buffer[buffer.length - 1][0]]
      ) {
        // console.log('here', count % 2);
        for (i = 0; i < buffer.length; i++) {
          board[buffer[i][1]][buffer[i][0]] = count%2;
        }
      }
    }
    buffer = [];
  }
}
 
// 돌을 놓을 수 있는 위치 파악해서, 검정색 가능위치(3), 하얀색 가능위치(2)로 board에 기록
function checkAvailable(){
  // 둘 곳 없어서 패스된게 두 번이 되면 게임 종료
  if(pass>1){
    console.log('game over')
    turnPass.style.visibility="hidden";
    gameOver();
  }
 
  // 이전 차례에서 2,3이었던 값을 -1로 초기화하는 함수 호출
  resetAvailable();
 
  boardCopy = board;
  // console.log(boardCopy)
  for (i=0;i<checkDirection.length;i++){
    for(k=0;k<checkDirection.length;k++){
      tX = i;
      tY = k;
  
  for (j = 0; j < checkDirection.length; j++) {
    // 검사할 좌표들을 aX,aY로 설정
    aX = tX;
    aY = tY;
    // 8방향으로 검사하는 루프
    while (aX >= 0 && aY >= 0 && aX < 8 && aY < 8) {
    aX += checkDirection[j][0];
    aY += checkDirection[j][1];
    // 검사 영역이 8x8 판 밖으로 나가면 멈추고,
    if (aX < 0 || aY < 0 || aX >= 8 || aY >= 8) {
      break;
    } 
    // 판 안에서 검사하는 칸들을 배열에 넣는다.
    else {
      buffer.push([aX, aY, boardCopy[aY][aX]]);
      // 검사한 칸의 색이 이번 차례의 색과 같거나, 빈 칸이면 배열에 넣기를 멈춘다.
      // 아니라면 검사한 칸의 값을 계속 배열에 넣는 루프를 반복한다.
      if (
        boardCopy[aY][aX] === count % 2 ||
        boardCopy[aY][aX] === -1 ||
        boardCopy[aY][aX] === 2 ||
        boardCopy[aY][aX] === 3 ||
        boardCopy[tY][tX] !== -1
      ) {
        break;
      } 
    }
  }
 
  // 만약 돌들의 배열이 돌을 놓을 수 있는 조건이면 3(검은색)이나 2(흰색)을 입력한다 
  if (
    buffer.length > 1 &&
    boardCopy[buffer[0][1]][buffer[0][0]] != count % 2 &&
    boardCopy[buffer[buffer.length - 1][1]][buffer[buffer.length - 1][0]] === count%2
  ) {
    // console.log(count);
    switch (count % 2 ) {
      case 0:
        board[tY][tX] = 2;
        break;
      case 1:
        board[tY][tX] = 3;
        break;
    }
    pass = 0;
  } 
  buffer = [];
}
}
}
}
 
function resetAvailable(){
  for (i=0;i<row;i++){
    for(k=0;k<row;k++){
      if(board[i][k]==2 || board[i][k]== 3){
        board[i][k]= -1;
      }
    }
  }
};
 
// 승리 화면 표시
function gameOver(x) {
  ending.play();
  // 음악이 재생되도록 약간의 시차를 두고 화면 표시
  setTimeout(() => {
    winScreen.style.visibility = 'visible';
    winScreen.style.zIndex = 2;
    if(blackScore > whiteScore){
      document.querySelector(".comment").innerHTML = "<br>" + "검정색 승리"
    }
    if(blackScore < whiteScore){
      document.querySelector(".comment").innerHTML = "<br>" + "하얀색 승리";
    }
    if(blackScore == whiteScore){
      document.querySelector(".comment").innerHTML = "<br>" + "무승부";
    }
 
    console.log(winScreen)
    troImg.style.animationName = "trophy";
  }, 300);
  
   
}
cs

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받을 수 있습니다.

+ Recent posts