도전기능 - 타워 판매 

 

타워 담당 포지션인 만큼, 해당 기능을 구현해 보기로 했다. 

가장 먼저 필요한 것은 캔버스에 그려진 그림을 특정하는 방법을 알아야한다.

 

많은 사람이 찾는 기능이라서 그런지, 검색 결과가 많았다. (다행)

 

 

우선 클릭 이벤트를 만들어줬다.

굉장히 보편적으로 사용태는 형태인 듯 하다.

 

이 부분을 잘 활용하면 ,

타워 뿐만 아니라 몬스터나 메인기지 등등을 선택했을 때에도 특정 이벤트가 실행되도록 하는것이

가능할 것 같다.

 

 

 

클릭 시, 해당 타워 오른쪽에 버튼 두개를 생성하도록 하고,

해당 버튼 클릭 시, findClick 으로 찾은 타워를 그대로 인자로 사용해서 함수를 실행하여 써먹으면 된다.

 

새로운 핸들러

 

31번 핸들러를 통해 서버로 타워 판매에 대한 요청을 하게되고,

위 핸들러 함수를 통해 이벤트가 처리된다. 

 

해당 핸들러 내에서는 payload를 통해 해당 타워의 type (어떤종류의 타워인지) 을 받게되고 

서버가 가진 meta data에서 일치하는 type을 찾아서 가격을 정보를 구해온다.

돈 계산을 적용하고 , ( 현재 하드 코딩으로 타워 구매 가격의 절반을 획득 )

 

그 후엔 Redis 에서 판매한 타워에 대한 데이터를 지우는 작업을 한다.

 

 

updateTower 를 통해 redis 데이터를 수정한다.

 

 

추가적으로 totalGold (서버에서 계산한 user의 소지금 을 보내서, 클라이언트에 적용 (갱신) 한다.

 

 

 

 

팀 프로젝트가 시작 !

 

사실 과제 발제는 10.08일 이었지만, TIL을 쓸 시간이 나지 않아서 이제서야 

해당 과제의 첫 TIL을 작성하게 되었다.

 

 

와이어 프레임

 

 

어디까지나 백엔드 트랙이기 때문에, 클라이언트에 해당하는 코드는 발제와 동시에 지급받았다.

 

지급받은 클라이언트는...

으음...

 

진짜.. 최고의 클라이언트.. 호우 

 

 

내가 담당한 부분은, 타워 구매에 관한 부분이고,

이미 발제일로부터 시간이 꽤 흐른 뒤이기 때문에 많은 부분이 진행되었다.

 

 

임무 중, 가장 핵심인 game.js 의 타워 구매 함수이다. 

 

이번 과제 중 가장 어려웠던 부분이 포함되어있는데,

towerData ... 어디서 얻어온 것인가 !

 

 

그것은, 클라이언트 내 socket.js 에 있는 response 의 .on  부분에서 

handlerId 2  를 포함하는 패킷을 받는다면, 해당 response 가 가진 데이터를 각각 파싱하는 부분이다.

( 위 코드는 리팩토링 예정에 있다. )

 

그럼, handlerId2 를 어디서 보내는가!

서버측에서, 게임이 실행과 같이 실행되는 gameStart 함수에서, 

데이터 베이스 (RDS) 에 있는 데이터를 전부 find 해와서 handler id 2로 보내주게 된다. 

 

해당 데이터들은 전부 메타데이터 형태이기 때문에, 게임 진행에 필요하므로 ,

서버에서 게임 시작 시 , 클라이언트에게 보내주게 되는 것이다 ! ★ ★ ★

 

 

결과적으로, 서버에서 데이터베이스를 조회하고 보낸 메타 데이터를 가지고,

클라이언트에서 타워 정보를 셋팅한다고 볼 수 있다.

 

타워 구매 후, sendEvent 를 통해 handlerId :30 으로 요청을 날리는데, 

현재는 towerType (구매 한 타워의 종류) 와 position (구매한 타워의 좌표) 를 payload 로 보내고

 

서버에서는 해당 데이터를 받아,  쌓아둘 데이터는 쌓아두고 (배열에 push) , 

검증이 필요한 부분은 검증을 하게 구현 해두었다.

 

현재는 setGold (서버가 기록하는 해당 유저의 gold 변화) 을 전부 합산한

 getTotalGold  가 0 미만 이 될 경우, 해당 유저를 치터로 간주하는 검증을 하고 있는 상태이다.

해당 Gold 검증 로직은 회의를 통해 변경 될 예정에 있다.

 

 

현재는 작업이 꽤 진행된 상태로, 이미지를 추가하고, 상점 UI 를 만들어 놓은 상태이다.

그러나, 아직 도전기능에는 손을 못대고 있기 때문에, 힘을 더 내야한다.

데이터 베이스 정규화 ( Normalization )

        목적

  • 테이블 내의 중복된 데이터를 제거
  • 데이터 추가, 수정, 삭제 간에 발생하는 이상현상( Anoamly ) 를 최소화
  • 일반적으로 3 NF 단계의 정규화를 마치면 정규화된 테이블이라고 표현

 

 

이상현상 ? ( Anomaly )

데이터의 중복 등 정규화가 진행되지 않음에 따라 발생하는 부작용을 말한다.

 

  • 삽입 이상 ( Insertion Anomaly )

데이터를 삽입하는데 불필요한 속성까지 추가해야하는 경우를 말한다.

- 기본 키가 { Student_id , Course_id } 인 경우, 어떤 수업도 수강하지 않는 학생의 경우

   Course_id가 없는 현상이 발생한다.

-  기본 키가 Null 이 될 수는 없기 때문에, "미수강" 이라는 불필요한 속성을 추가해야하는 경우가 발생.

 

  • 갱신 이상 ( Update Anomaly ) 

데이터를 갱신한 후 일관성이 위반되는 경우

-  어떤 학생이 개명을 통해 Student_Name 이 바뀌었다고 할 때,

   해당 개명학생의 모든 튜플을 갱신해줘야만 한다.

   하지만, 이렇게 하지 않고 하나의 튜플만 갱신할 경우 발생하는 문제를 '갱신 이상' 이라고 한다.

 

  • 삭제 이상 ( Deletion Anomaly ) 

데이터를 삭제하는데 의도치 않은 항목까지 함께 삭제되는 경우

-  수강 취소를 위해 수강 정보를 삭제하려고 할 때,

   수강 정보와 학생 정보가 하나의 튜플에 같이 들어있다면, 

   수강 정보는 물론 학생의 정보 또한 삭제되어버린다. 

 -  위 처럼, 원하지 않는 데이터도 함께 삭제 되어버리는 현상을 '삭제 이상' 이라고 한다.

 

 

함수적 종속성 ( Functional Dependency  , FD )

함수적 종속성이란, 테이블에 있는 두 개의 속성들 사이의 제약을 말한다.

테이블에 X , Y 필드가 있다고 했을 때, 

X 의 값이 Y에 속한 '하나의' 값에만 매핑이 되는 경우를 함수 종속적이다 라고 하며,

X -> Y 로 표현한다. 

이 때, X를 결정자, Y를 종속자 라고 한다.

 

 

완전 함수 종속 ( Full Functional Dependency ) 

기본키가 종속자이며, 기본키가 여러속성으로 구성이 되어있을 경우, 

기본키를 구성하는 모든 속성이 포함된 부분집합 또한 종속자일 경우

 

 

부분 함수 종속 ( Partial Functional Dependency )

릴레이션에서 종속자가 기본키가 아닌 다른 속성에 종속되거나, 기본키를 구성하는

여러 속성들의 부분집합 중 일부분에만 종속될 경우.

 

위 테이블에서, 기본키는 { 고객 id  , 상품 id }  이며, '주문 상품' 의 경우,

상품 id만 알아도 식별이 가능하기 때문에, 주문 상품은 기본키에 부분 함수 종속 관계 이다.

 

'가격' 의 경우, 상품 id 에 부분 함수 종속 관계 라고 할 수 있지만, 

이 경우에는 기본키 외에도 '수량' 에도 종속이 되어있다.

이 경우에는 논리적으로 자연스럽지 않으며, 개선될 필요가 있다.

 

이행 함수 종속 (Transitive Functional Dependency )

X  Y  Z  3가지 부분집합을 가진 릴레이션에서 ,

X -> Y  이고, Y -> Z 일때,  X-> Z 가 성립이 되는 경우를 말한다.

( Y- > X가 아닐 경우 )

 

위 테이블을 보면, 

'가격' 은 { 고객 id , 상품 id -> 수량 -> 가격 } 이러한 관계라고 볼 수 있으며,

 { 고객 id , 상품 id } 에 이행적 종속 되어있다 라고 할 수 있다.

기본키인 상품 id와, 기본키가 아닌 수량이 있어야만 가격이 유효할 수 있다는 것이다.

이러한 관계 또한 논리적으로 부자연스럽기 때문에 

부분 함수 종속과 이행 함수 종속을 제거하여 테이블을 논리적이고

효율적이게 만드는 것을 정규화 라고 한다.

 

 

 

오늘 TIL 의 주제는 트러블 슈팅이다.

 

 

위 코드는 10.02일에 만들어 놓은 코드이다.

선인장의 등장 자체를 패턴화 시키기 위해 사용한 방법으로,

for 문과 setTimeout 이 사용되었다.

 

문제점 

1. setTimeout 

현재 과제에서는, 전방위적으로 deltaTime을 이용하여 시간의 흐름을 계산하고 있다. 

setTimeout 의 ms와는 방식이 다르기 때문에 개별적으로 흘러가는 두 종류의 Time 이

언제 엇나가게 될지 모른다.

 

실제로, 위의 for문 + setTimeout 의 형태를 유지한 채로, 원하는 박자의 게임 흐름을 구현하려고

했으나, 싱크가 맞지 않아 계속해서 실패했다.

 

해결방안

우선 createCactus 의 값을 초기 상태로 돌려주었다.

한 번의 createCactus 실행으로 패턴을 형성하는게 아니라, 

createCactus 자체를 여러번 실행하게 만들어 보려고 한다.

 

 

update 에서 받고있던 deltaTime 과,

간이 인덱스 (?) 를 확용해서 일종의 유사 반복문을 구현해 보았다.

 

가독성이 좋지 않지만, 간단하게 해석하면,

최초 1600  rhythm 이 될 때 까지 대기 ( 1프레임 당, deltaTime*gameSpeed 만큼 증가 )

 

1 비트에 200 rhythm  기준으로 셋팅

pattern [0] 값이  0 인 경우, 선인장  생성하지 않음 

pattern [0] 값이 0 이 아닌 경우, 선인장 생성 ( 효과음도 출력 )

patternIndex ++ 을 통해, 다음엔 pattern[1] 값 참조

 

 

pattertnIndex 를 ++ 해줘서 다음 1400 -> 1600 rhythm  이 되면 두번 째 index 패턴 실행

 

pattern.length 는 반드시 8이기 때문에, 

8까지 모두 실행했으면, 새로운 패턴을 선택한다.

 

cactus_pattern 또한 수정했다. 

pattern 0은 선인장이 등장하지 않는 값,

             1은 선인장이 나오는 값    이다.

 

의도대로 박자가 잘 맞게 되었다.

게임 플레이

 

 

 

아이템 획득 속도에 대한 검증 

클라이언트 조작을 통해 아이템을 무분별하게 등장시켜 획득 할 수 있다.

 

간단하게, 이전 아이템 획득 시간과 현재 획득한 아이템의 획득 시간을 비교하여

비 정상적으로 빠른 속도로 아이템을 습득할 경우, fail을 반환하도록 해 보자.

 

 

해당 코드는, 아이템 획득 시간과 점수의 합에 대한 검증이다.

 

해당 아이템을 획득하기 이전에 획득한 아이템의 획득 시간과 점수의 합을 비교하는 코드이다.

 

문제점

위 코드는 스테이지 점수 획득 검증의 형태를 따라한 것인데,

처음 게임 시작할 때와 첫 아이템 습득 시에는

 

 items 가 빈 배열이기 때문에, currentItemId 를 찾을 수 없게 된다.

 

해결 방법

본문에 나온것 처럼, if 문을 사용하여, currentItemId 값이 존재할 때, (items 가 빈 배열이 아닐 때)

에만 해당 검증을 진행하도록 했다.

 

 

 

필수 기능이 모두 구현되었다.

 

오늘은 선택 기능인,  선인장 패턴만들기를 시도해보자.

 

선인장을 일정한 패턴으로 등장하게 하고싶다

 

 

 

일단 선인장 생성을 담당하는 녀석에게 가보자.

 

해당 부분이 본격적으로 선인장이 생성되는 순간이다.

저 cacti 에 push 되는 순간, 필드에 등장하게 되는 것.

 

 

TMI ... ↓

더보기

눈을 씻고 찾아봐도 선인장이 2개 연속등장하는 코드가 없다.

해당 코드를 보면 힌트가 될 수 있으거라 생각했는데 없다.

 

 

이상하다..  분명히 인 게임에선 가끔 2연속 선인장이 등장했는데...

 

자세히 보니 선인장 모양이 다르다.

 

 

애초에 모델 이미지 차제가 저 모양이었던 것

 

 

 

우선 위와 같은 데이터 테이블을 추가해주었다. 

stage_id 에 현재 stageId 를 포함하고 있다면, 해당 pattern 이 등장할 가능성이 생기는 형태

 

여기서 pattern 의 length = 등장하는 선인장 수, 

숫자는 해당 선인장의 등장 딜레이 로 정했다.

 

이제 pattern 에 따라 선인장을 생성해주기만 하면 되는데. 그것이 쉽지가 않다.

 

        for (let cac of pattern) {
                () => {
                    const cactus = new Cactus(
                        this.ctx,
                        x - 240,
                        y,
                        cactusImage.width,
                        cactusImage.height,
                        cactusImage.image,
                    );

                    this.sound();
                    this.cacti.push(cactus);
                },
            );
        }

 

반복문을 사용하면 그럴싸 한데?

문제는, 반복문으로 생성하면 선인장이 한번에 다 생성되어 버린다.

 

pattern 값에 따라 x축에 변위를 주면?

 

x 좌표가 다르게 생성되기 때문에, 패턴의 형태가 갖춰질 수 있다.

그러나,  어디까지나 한번에 생성되는것이기에 원하는 방향이 아니며,

sound는 어차피 한번만 재생되게 된다.

 

다른 여러가지 방법을 찾아보다가...

 

밑져야 본전으로,  setTimeout 을 사용해보도록 했다.

 

setTimeout 에 대한 이해가 부족했기 때문에, 

현재 환경에서 정상적으로 작동하지 않을것이라고 막연하게 생각했는데,

 

놀랍게도 잘 작동한다.

for문 자체는 마찬가지로 순간에 반복되지만, 

 그 결과, delay 가  함수가 스택에 쌓여서 delay마다 실행이 되는 형태인 듯 하다.

 

다만, setTimeout 의 계산은 

 

현재는 delay 에 추가적으로 gameSpeed를 나눠서, 게임이 빨라지면 패턴 간격도 그에 따라 빨라지게 구현해보았다.

 

 

위의  배열로 선인장이 등장한 모습이다.

7에 해당하는 선인장은 아직 등장하지 않은 모양 !

 

다음 목표는 공격(?) 하여 선인장 제거하기 기능이다.

'내일배움캠프' 카테고리의 다른 글

24.10.07 TIL 데이터 베이스 정규화  (0) 2024.10.07
24.10.04TIL 리얼타임 과제  (0) 2024.10.04
24.10.01 TIL 리얼타임 과제  (0) 2024.10.01
24.09.30 TIL 리얼타임 과제  (0) 2024.09.30
24.09.27 TIL 웹 소켓 입문  (0) 2024.09.27

 

 

 

오늘 구현해야 할 부분으로 필수 과제에 해당한다.

 

item_unlock.json

해당 기능을 구현하기 위해 테이블을 약간 수정해서,  item_id를 배열로 갖도록 했다.

이렇게 하면  stage_id에 해당하는 등장 item_id를 명시하기 쉬워진다.

 

 

이제 아이템을 생성하는 ItemController 으로 가서, 해당 부분을 수정하면 된다.

 

기존에는 스테이지에 관계 없이  Item.json 에 있는 데이터중 랜덤하게 생성이 됐었다.

 

json을 import 하는 과정에서 1차로 헤맸다.

 

 

 

수정한 아이템 생성 부분 !

 

첫 번째 문제 

스테이지는 Score.js 에서 변화하고 관리됐기 때문에, 거기서 사용한 stageId 를 가져와야한다. 

그렇다면, createItem을 호출하는 부분을 찾아보자.

 

바로 아래에 있었다. 

위 이미지에는 해결 방법으로 선택한 score 를 운반하는 모습이 나타나있다.

그러면, update를 호출하는 부분을 찾아보자.

 

index.js

클라이언트의 초기 세팅에 관한내용이 있는 index.js 에서 찾아볼 수 있었다.

 

이 index.js 윗부분에서는, 

 

setScreen 이라는 함수가 호출이 되고있고, (기본 canvus의 범위를 지정하는듯 하다)

 

또, 그 내부에서 호출하는 createSprites 함수 에서

 

거의 모든 Class 객체를 생성하는 관경을 목격할 수 있다.

 

그렇다면 ,

위의 부분은, 

gameLoop 함수 내에 있고, 

 

해당 함수의 호출은 index.js 에서 가장 마지막에 이뤄지기 때문에, 

 

score 객체는 이미 생성된지 오래라는 말이 된다. 

 

따라서, 이미 이미지에 표시되어있는 것 처럼, 

score가 생성된 score 를 ItemController 에서 참조할 수 있도록 운반해 주었다. 

 

 

잘 운반했다면, score가 알려준 stage 와 일치하는 값을 item_unlock.json 에서 찾아서 

 

해당 스테이지에 포함되는 item_id 의 배열 중 랜덤하게 하나를 뽑도록 만들었다.

 

 

ID 가 들어갈 위치에 getRandomItem 으로 뽑은 숫자를 가져오면 된다.

 

 

 

두 번째 문제 

 

여기에서, 아이템 종류 별 획득 스테이지 검증   부분이다.

 

 

 

검증 로직 자체는 만들기 어려운것이 아니다. 

문제는, 아이템이 생성될 당시에 스테이지 와 획득할 당시 스테이지가 상이할 수 있다는 점..

 

먹을 때 쯤이면 2스테이지가 된다.

 

1스테이지 에서 생성된 아이템인데, 먹을 때는 2스테이지가 되어있을 수 있다는 말이다.

 

따라서, payload 가 아이템을 획득하는 시점의 stage 를 가지고 있다면, 

검증하는 과정에서 문제가 생길 수 있다. 

 

(1스테이지에서만 나오는 아이템이 1스테이지에서 생성됨 

-> 먹을 때 쯤, 2스테이지가 되었음 

-> 2스테이지에서는 나올 수 없는 아이템을 획득함 )

 

해결법?

 payload 가 아이템 획득 시점이 아닌, 아이템 생성 시점의 id를 뱉도록 만들어 주면 된다. 

 

문제는... 어떻게? 

Score 가 갖는 stage 는 즉각 변경이 되기 때문에, 해당값을 payload로 보내면

아이템을 획득하는 시점의 stage 에 해당하게된다.

 

생성된 아이템으로부터 각각 생성된 시점의 stage를 얻어오려면,

item 자체가  stage 를 갖게 할 수 밖에?

 

 

Item Class 가 stage 를 갖도록 만들어 주었다 .

 

위와 비슷한 방법으로 아이템 객체의 stage 를 가져와서

currentStage 에 보내주면, 이제 아이템은 생성된 시점의 stageId를 갖고있고, 

그 stageId를 통해 검증을 진행하게 된다. 

 

'내일배움캠프' 카테고리의 다른 글

24.10.04TIL 리얼타임 과제  (0) 2024.10.04
24.10.02 TIL 리얼타임 과제  (0) 2024.10.02
24.09.30 TIL 리얼타임 과제  (0) 2024.09.30
24.09.27 TIL 웹 소켓 입문  (0) 2024.09.27
24.09.26 TIL 웹 소켓 입문  (0) 2024.09.26

 

 

배운 내용을 토대로, 슬슬 필수 기능부터 과제를 시작하기로 했다.

 

클라이언트와 서버를 오가는 과제라서 그런지... 

기초적인 부분을 놓치거나, 방향을 못잡고 시간을 낭비하는 일이 굉장히 잦다.

 

아무튼 그 시작점은 ...!!

'스테이지 점수' 의 데이터가 올바른 데이터인지 서버에서 검증하는 과정이다.

우여곡절이 굉장히 많았기 때문에, 이것 저것 console.log를 다 찍어보게 되었다.

우여곡절을 전부 설명하려면, 모든 일과를 TIL에 기록해야 할지도 모른다.

 

 

클라이언트도, 서버도 미리 세팅된 json 파일을 데이터 테이블 삼아서 요청과 검증을 하기 때문에, 

일반적으로 생각했을 때는 당연히, 검증하나 마나 같은 데이터겠거니 하겠지만 !

 

클라이언트는 언제 어떻게 변조 될 지 모르는 일이고, 고의로 변조하지 않더라도

네트워크 상태에 의해서 지나치게 상이한 데이터가 서버로 넘어올 수 있기 때문에,

서버는 늘 클라이언트가 유효한 데이터를 보내고 있는지 검증해야 하고, 

유효한 데이터만을 저장할 수 있어야한다.

 

검증 과정 자체를 잘 ! 구현하는것이 서버의 목적이자, 이번 과제의 핵심이다.

 

위의 검증으로 인해 찍히는 console.log 를 살펴보면

 

 

이와 같이 구성되어 있는데, 

서버가 예상한 스테이지 점수와 클라이언트가 보내온 점수를 비교하여 검증하는 것이다.

 

첫 번째 문제점

서버가 계산할 때, 네트워크 차이로 인한 오차가 발생하는데 (시간을 소수점까지 계산하기 때문),

오차가 있는 숫자를 계속해서 더해 나가는 형태이기 때문에, 오차 자체가 누적되어 버린다.

플레이타임이 늘어날 수록, 오차가 계속해서 늘어나 언젠간 지정한 오차범위를 넘어가버릴 것이다.

 

서버의 계산에서 소수점을 제외하자니, 검증의 정확도가 떨어진다는 느낌이 드는것이다...

 

나의 선택

 

검증의 범위를 해당 스테이지에서 얻은 점수로 한정한다고 하면,

굳이 누적된 오차를 비교할 필요가 없다. 

이번 스테이지 점수 검증 과정에서 별다른 문제점이 없었다면, payload.stageScore 를 

신뢰할 수 있다고 보는것이다.

 

따라서, 다음 스테이지의 점수 계산시에, payload.stageScore+ 해당 스테이지 예상점수

를 계산하도록 했다.

 

 

그렇게 하면, 모든 오차가 누적되지 않고 해당 스테이지에서 얻은 점수의 오차만 확인이 가능하게된다.

 

 

문제는 다음이다.

해당 과제에서는 점수를 얻는 방법이 두 가지 이다.

생존하기만 하면 무조건 오르는 스테이지 점수와, 아이템을 통해 획득하는 점수이다.

 

 당연하게도, 아이템을 통해 얻은 점수 또한 검증을 필요로 한다.

 

위의 스테이지 점수와 마찬가지로, 

클라이언트가 보낸 아이템 획득 정보와 그 아이템의 점수, 

그리고 서버가 예상한 해당 아이템의 정보와 아이템 점수를 비교하여 검증하고,

하는김에 아이템 점수 총합 까지 검증하면 될 것 같다.

 

어디서 검증하지? 

위의 스테이지 점수는 moveStageHandler 를 통해 진행되므로, 

검증 타이밍이 스테이지 변화 시 이다.

점수가 증가하는 매 순간마다 검증할 수는 없는 노릇이고,

스테이지가 바뀔 때마다  시간 당 획득 점수가 증가하게 설정되어있기 때문에,

스테이지가 바뀌는 시점이 나름 적절한 점수 검증 타이밍 이라고 볼 수 있다.

 

그럼 여기에 추가로 아이템 점수까지 같이 검증하면?

 

안될 건 없지만, 뭔가 애매하다

아이템이 초당 수십개씩 쏟아지는것도 아니고, 

무엇보다 아이템 획득 시 라는 명확한 타이밍이 존재한다.

따라서, 

 

Score 객체에 

update( 스테이지 변화 ) 타이밍에 스테이지 점수 검증을 진행했던 것 처럼, 

 

getItem ( 아이템 획득 시 ) 타이밍에 아이템 점수 검증을 진행하는것이 적절한 선택인듯 하다.

 

 

방법 자체는 똑같기 때문에, 그대로 따라 쓰면되겠다.

라고 생각했던 때가 있었는데요.. 생각보다 쉽지 않더라구요...

 

시작은 수월하게 sendEvent 부터! 역순으로 진행하는것이 마음이 편한듯 하다.

 

여기서 12번 이라는 새로운 핸들러는 getItemHandler 라고 이름지어줬다.

 

새로 만든 핸들러를 

moveStageHandler 가 살고있는곳에 같이 만들 이유가 딱히 없으므로, 

getItem.handler.js 를 새로 만들어주고...

 

 

stage.model.js

 

위처럼 하기엔 짜치기 때문에 item.model.js 도 새로 만들어준다.

간단하게 만들 생각이었는데 일이 점점 커지는 듯 하다...

 

중간 과정을 잠시생략하고, 

 

model 과 핸들러를 임시로 구현하고 , 테스트를 해봤는데 !

 

두 번째 문제 !!

계속해서 push 에러가 발생한다 .

 

일반적으로 위의 push 에러는, push의 대상이 되는 items[uuid] 가 정상적인 배열 형태를 갖추지 못했을 때 나타난다.

 

불과 몇 시간 전의 나 이지만, 정말 바보같지 않을 수가 없다.

( 에러내용만 자세히 읽어봤어도 안 헤맸을 것이다 )

 

가장 기초적인 원인인 

↑   를 생각하지 못하고 엄한데서 꽤 긴 시간을 낭비했다.

 

초기값 set 문제인가 ?!

 

getItem 을 한번 해줘야 하나? ( 그게 무슨 말이니...흑흑.. )

 

 

어떡하지? 하고 Zep의 과제 발제자이신 튜터님 을 한번 쳐다보았는데...

 

데이터를 Push 넣기 전에 틀 =  바구니를 먼저 생성 해줘야 합니다 !!!

강의에서 그렇게 강조해서 말했는데 !@ => 라고 상상속 튜터님에게 혼났다..

 

아바타와 눈이 마주쳤을 뿐인데 가르침이 생각나는 당신은 대체...

 

 

Stage는 바구니가 있었다구

 

 

 

 

 

 

그 즉시, createItem 을 같은 형태로 만들어서 바구니를 생성했다.

 

이런 기초적인 실수를 하다니 !

 

남은 시간은 임시였던 위의 아이템 점수 검증 로직을 마저 완성해보도록 하자 !

 

 

 

 

강의를 모두 듣고, 또 ' 그 대사 ' 를 할 때가 됐다.

 

이걸 어떻게 하라고 ?!

 

익숙해 질 때도 됐지만, 과제가 시작할 때마다 위기가 느껴지고,

어떻게 과제를 해결 할 수 있을까 고민하게 된다.

 

 

      나는 벌써 이 상황

 

 

 

현업을 하는 사람에겐 아무것도 아니겠지만, 

handler 의 등장으로 낯선 모습의 디렉토리 구조가 되었다.

 

현재는 강의에서 알려주는 부분을 따라 친 수준밖에 되지 않기 때문에, 

 

오늘은 간단하게 강의를 들은 내용을 상기시켜며 작동 순서 정도만 체크하도록 해보자.

 

 

늘 그렇듯, 시작은 src/app.js 부터 !!

 

이번에 추가된  핵심 내용으로, 웹 소켓 연결을 위해 호출하는 부분이 추가되었다.

 

createServer를 통해 서버에서 필요한 정보를 가져와 웹소켓에 init 하는 과정이다.

 

 

socket.js

initSoket(server) 로 실행되는 부분은, 

본격적으로 new SocketIo 로 만들어진 io 객체에 server 의 정보를 집어넣고

핸들러 부분으로 넘어가게 된다.

 

register.Handler.js

 

registerhandler에서는 본격적으로 클라이언트 구동에 필요한

user 와 stage 생성에 필요한 uuid를 이 단계에서 뿌려주고,

 

register.Handler.js

 

중요한 것은 이 부분 !!

웹 소켓의 주요 기능중 하나인 .on  함수이다.

 

위의 socket.on('event'....  함수는,

'event' 라는 이름으로 불려지기 전까지 기다린다..  라고~ 일반적으로 표현한다.

 

만약 클라이언트로 부터 'event' 이름으로 데이터가 들어오게 되면, 

기다렸다는 듯이 (맞는말)  io 와 socket과 data 정보를 가지고 handlerEvent를 실행하게 된다. 

여기서 data는 클라이언트가 보낸것이다.

 

helper.js  (이제 팔이 모자라서 helper 라고 쓰는듯 하다 )

 

그렇게 handlerEvent 함수가 실행되면, 약간의 검증 절차 이후에...

찐막

handlerMapping.js

이제 마지막으로,  클라이언트에게 전달받은  handler ID를 통해 

 

해당하는 찐막 핸들러 함수를 호출하게 된다.. 

 

handler() 는 그렇다면 무엇인가..! 

그냥 handlerMapping 에서 해당 data의 value 즉, 

2로 요청할 경우 gameStart() 를 실행하게 되는

뭔가 유사 고차함수(??) 같은 느낌의 구문이다.

 

 

game.handler.js

 

드디어 목적지 까지 왔다.

 

클라이언트는 그저 'event'  , 2  ++ 몇가지 정도만 보냈을 터인데, 너무 험난한 여행이었다.

( 현업 입장에서 생각한다면 오히려 도로가 잘 개통되어 있다 라고 하겠지?... )

 

만약 gameOver 에 처리에 관한 요청이었다면, 점수를 정산해주고, 데이터 베이스에 기록하는 작업이

여기에서 이루어 진다고 볼 수 있겠다. 

 

 

 

 

 

다음은... 클라이언트다.

 

 

 

클라는.. 내일 알아보도록 하자! (내일배움캠프)

 

 

 

과제의 끝은 새로운 과제의 시작   이 되어버렸다..

 

 

약간 과장해서

사이드뷰 플랫폼 점프액션 게임 

이라고 할 수 있다.

 

물론 당장 만드는 것은 아니고 , 학습이 우선 !!

 

 

이번 CH4 에서는 총 3개의 강의가 지급되었다.

 

 

 

본격적으로 서버환경을 구성하기 전 알아야 할 CS 강의가 포함되어 있고, (두렵다)

 

사이드뷰 플랫폼 점프액션 게임 

을 만들기 위해서 주특기 심화 강의를 먼저 듣도록 했다.

 

 

Realtime 즉 , 실시간 이다.

 

웹소켓을 이용해 실시간으로 상호작용하는 서버 환경을 구현하는것이 해당 과제의 목표이다.

 

웹소켓의 통신은 http의 통신과 많은 차이가 있는데, 

가장 먼저, 본격적인 연결형 통신이라는 것이다.

 

http는 req 건네고 res 받았으면 끝이다. 즉, 비 연결형 통신이다 .

 

이 말은,

동일한 클라이언트에서 req 를 여러개 보낸다고 해도, 

그것이 동일한 클라이언트 인지는 그 자체로 서버가 구분할 수 없다. 각각 새로운 연결이기 때문 !

 그렇기 때문에, http 에서는 매 req, 매 res 마다 위와 같은

나는 누구고~ 이런사람인데~ 이것 좀 주쇼~  등과 같은 정보가 담긴  

'비교적 무거운' 패킷이 매번 전달되어야만 한다.

 

물론, 이 형태가 무조건 비효율 적이고 도태된 형태라고 할 수는 없다.

어디까지나 http는 웹 페이지를 위해 만들어졌으며, 추가적인 개선도 진행되고 있다. (버전 업)

 

다만, 우리는 새로운 목적이 생겼을 뿐이다. 

 

클라이언트 상에서 실시간으로 데이터를 주고받고 즉각 표시 해 줄 수 있는것이 필요했고,

그것을 위해 개발된 것이 웹 소켓인 것이다.

 

 

추가적으로, 본문에서는 HTTP 의 단점 중 ,

클라이언트의 요청이 없으면 서버가 자체적으로 할 수 있는게 없다는 내용도 있었다.

 

내일부터는 위 사항들을 잘 기억하고 

 

실습에 들어가도록 하자 !

(공룡 ! 공룡 ! )

 

 

팀 과제가 무사히 마무리 되었다. 

 

 

 

 

 

 

 

 

 

 

 

최종 목표였던 브라우저에 canvas 그리기 까지 달성하였다.

 

물론 완성도 면에서는 아쉬움이 남지만, 기획 당시만 해도

 

과연 가능할까? 

어차피 필수 요구사항도 아닌데, 일단 도전기능 마무리가 되면 그때 조금씩 해보죠 !

하는 정도의 분위기 였다.

 

 

불가능을 가능케 하는 힘 !!

 

이번 과제를 진행하면서 가장 크게 느낀것은

팀 내 분위기가 정말 중요하다는 것이다.

과제가 시작되자마자,  마치 사전에 합의된 것 처럼

각자 생각한 기획에 대해 의견을 나누고, 

와이어 프레임과 API 명세서의 초안, ERD 가 자연스럽게 그려지기 시작했다 .

 

초반에 '의문의 변수 발생' 으로 인해,  브라우저 구현과 게임 내용의 구현까지 목표로 하게되어

불안감이 생기기도 했지만,

 

이내 팀장이 정해지고, 역할 분담이 이뤄지자 , 계획은 차츰 진행되기 시작했다.

 

소통 

우리 조는 과제를 진행하는 내내 오픈 마이크를 유지했으며, 팀원 모두가

상대를 배려하는 말투와 태도로, 소통하는데에 부담이 전혀 없었다.

 

문제가 생기면 그때 그때 도움을 요청하기도 하고, 

잠시 여유가 생긴 팀원이 다른 팀원의 진행을 도와주기도 하는 등,

상당히 이상적인 모습으로 협업이 진행되었다.

 

 

 

어떻게 해서 가능했을까?

 

협업에 능통한 5명이 모였기 때문에 가능했을까? 아니다.

리더쉽 이론을 마스터한 지휘관이 팀장이 되었기 때문일까? 아니다 !!

 

5명 모두가 이러한 환경이 될 수 있도록

적극적인 참여서로를 배려하기 위해 노력 했기 때문이다.

 

나 또한 협업에 대한 경험이 부족하기 때문에 망설임이 있었지만,

캠 또는 화면 공유를 통해, '소통이 가능한 상태' 와, ' 진행중인 내용' 을 간접적으로 보여주어,

좀 더 협업에 유리한 분위기가 조성될 수 있도록 노력했다.

 

지금의 경험을 잊지 말고,  적극적인 자세와 태도를 하기 위해 노력하자 !

 

 

 

아쉬운 점이 있다면..

언급한 내용처럼, 최초의 기획과 달리 브라우저 환경을 구성하도록 목표가 바뀌었고,

해당 부분은 과제에서 요구하는 사항이 아닌데다가, 관련 지식도 부족한 상태였기 때문에

결국 브라우저 환경을 구성하는데 있어서 별다른 도움이 되지 못했다. 

때문에, 도전 기능을 완성하고 모두가 브라우저 환경 구현에 몰입할 때에 ,

내가 할 수 있는 일이 없다고 생각되어 아쉬웠다.

 

 

 

그래도 한게 없는것은 아니다 !

거의 사륜안 모드로 코드 작성

 

위의 버튼들은 내가 맡은 선수 관리 파트의 API 를 각각 호출한다.

 

 

 

인솜니아를 표방한 형태를 계획했기 때문에,

params 와 Body를 입력하여(필요한 경우에만)  API를 호출할 수 있다.

 

 

 

자신의 편성 조회하기

추가적으로 브라우저 환경 스러움(?) 을 챙기기 위해, 

조회 기능 내에도 버튼을 만들어 API 를 호출하도록 했다.

 

맞다.. 첫 과제에서 사용한 그 button onclick="name('value')" 이다.

 

이전에 진행된 첫 팀 과제에서 비슷한 기능을 사용하기도 했고,

게시판 만들기 강의에서 비슷한 내용을 배웠기 때문에 구현이 가능할것으로 판단했다.

 

그 결과, 선수 편성 API 에 대한부분은,

사실상 보유 선수 관리  하나로 전부 해결 할 수 있게 되었다.

 

보유선수조회-> 보유선수 관리로 변경된 셈.

 

보유 선수 모두 조회 기능에서 선수관리의 모든걸 해결

 

 

저 부분을 구현하면서 가장 힘들었던 부분은... ? 새로고침 !!

 

편성에서 추가, 제외 혹은 판매 했을 때,

데이터가 변경되었어도 보이는 것은 이전 그대로 이기 때문에,

새로고침을 해야하는데, 

 

위 같은 함수를 사용하면 페이지 전체가 새로고침되기 때문에 의미가 없다.

 

특정한 Div의 내용만 새로고침 해야하는데, 

문제는.... 새로고침은 그냥 간단한 갱신 아니라  다시 불러옴에 가까웠고, 

그 새로고침을 하기 위해서는, 저 버튼이 눌렸을 때 실행되는 함수를 다시 호출하거나, 

아니면 수동으로 다시 띄우거나 해야했다. 

 

다른 버튼의 코드 예시

호출(인자).then(res => {})

함수의 구조가 지금까지 봐왔던것들과 달라서, 재귀가 가능하긴 한건지도 잘 모르겠고 ...

해석이 잘 안됐기 때문에 최초에는 내용 갱신에 필요한 코드를 전부 복사해서 붙여 넣었다.

      //새로고침 되어야 할 부분
       resContext.innerHTML = '';
       res.data.forEach(player => {
       resContext.innerHTML += `
            ${player.isPicked === true ? '▼' : ''}
            선수명 [${player.playerName}]
            ${player.isPicked === true ? `[편성중]` : `보유 수량 : [${player.playerQuantity}]`}
            ${player.isPicked === true ? `<button onclick="excludePlayer('${player.playerId}')">편성제외</button>` : `<button onclick="addPlayer('${player.rosterId}')">편성추가</button>`}
            ${player.isPicked === true ? '' : `<button onclick="sellPlayer('${player.rosterId}')">선수 판매</button>`}
            <button onclick="enhancePlayer('${player.rosterId}')">선수 강화</button>
            <br><br>
            `;
      });

 

 

 

예를 들면, 편성 제외 버튼의 함수에서, 편성을 제외한 이후에 저 코드를 복붙한 것이다. (느헉)

 

대충 이런 형태가........ 

 

물론 어디까지나 임시 이지만,

정말 민망하기 그지없는 코드이다.

 

 

 

검색을 하다보니, then() 은 굉장히 특별한 함수이고 어려운듯 해 보이지만,

 

당장은 promise 결과를 다시 반환해주는 함수 라고 이해하기로 했다.

 

그렇다면!!

그렇게 반환받은 결과로 다음 구문을 실행하는 중인 것이었고,

그렇다면 당연히 그 부분에 내용대신 함수가 들어갈 수 있겠다.

 

반복이 필요했던 부분을 함수로 만들어주면 된다

 

 

 

물론, 이것도 그렇게 올바른 방법인지는 모르겠지만,

최초의 코드와 비교했을때 원숭이와 유인원의 차이라고 생각이 되기에

일단 이정도로 만족하기로 했다. 

 

위 내용은 하필 브라우저 부분이라, 

과제 발표의 트러블 슈팅에도 포함 하기가 애매했다.

 

 

추가적인 트러블 슈팅 내용도 많지만 오늘은 일단 여기서 끊도록 하자 !

 

+ Recent posts