각종 후기/우아한테크코스

[우아한 테크코스 3기] LEVEL 2 회고 (73일차)

제이온 (Jayon) 2021. 4. 15.

안녕하세요? 제이온입니다.

 

오늘은 우테코를 온라인으로 진행하는 날이지만, 수업이 없는 관계로 검프와 낙성대역 인근 카페에서 만나 페어 프로그래밍을 진행하였습니다.

 

 

데일리 미팅

제이슨 조에서의 두 번째 데일리 미팅입니다. 오늘의 데일리 미팅 진행자는 포츈이었고, 꽤나 독득한 게임을 준비해 주셨습니다. 포츈을 제외한 나머지 크루는 포츈에게 '남들이 안 해 봤을만한 자신의 특이한 경험'을 DM으로 전달하고, 각자가 이러한 특이한 경험을 누가 했는지 맞혀보는 게임이었습니다.

 

마라톤 3.8km를 16분 안에 완주를 했다던지(16분 맞나?), 지구 최남단 스타벅스에 방문을 했다던지, 우리 파피가 술 많이 마시고 길거리에서 잤다던지 등등 꽤나 독특한 경험들이 많았습니다. 저는.. 7살인가 8살 때의 피아노 콩쿠르 대회에서 악보 없이 연주하다가 도중에 음을 까먹은 일화를 소개하였습니다. 그때, 너무 당황한 나머지 머리를 붙잡으며 연주를 멈췄는데, 저를 보던 관객들이 비웃던 경험이 아직도 생각이 납니다. 참.. 지금 생각해도 영 별로인 경험이네요.

 

아무튼, 그 외에 특이한 경험의 주인을 딱 2명 찾으면서 아쉽게 2등을 차지하였습니다. 1등은 3명을 찾은 사람이며 공동이었기 때문에 사다리 타기를 통해 파피가 최종 우승은 안됐습니다. 그리고 아쉽게 우승이 안 된 파피가 내일 데일리 미팅을 아론으로 예고하였습니다. 참고로, 최종 우승은 라이언이며 상품은 뭘 받았는지 제가 잘 못 들어서 모르겠네요.

 

 

스프링 입문 - 체스 미션 페어 프로그래밍

어제 남은 체스 도메인 기능과 스파크 자바 및 JS를 합치는 작업을 진행했습니다. 남은 도메인 기능은 턴 확인, 이동 가능 유효성 검사, 점수 계산이었고 오전 동안 턴 확인과 이동 가능 유효성 검사를 수행했습니다.

 

기존의 제 코드에서는 턴을 단순히 처음에 화이트로 설정하고, 무한 루프를 돌면서 블랙, 화이트, 블랙, 화이트를 외부에서 바꾸는 식으로 구현했습니다. 하지만, 이번에는 검프의 상태 패턴을 응용해 보았습니다.

 

 

    public void moveByTurn(final Position sourcePosition, final Position targetPosition) {
        if (whitePlayer.isFinish()) {
            move(sourcePosition, targetPosition, blackPlayer);
            chessBoard = chessBoard.put(whitePlayer.pieces(), blackPlayer().pieces());
            return;
        }
        move(sourcePosition, targetPosition, whitePlayer);
        chessBoard = chessBoard.put(whitePlayer.pieces(), blackPlayer.pieces());
    }

 

 

위 메소드는 ChessGame 내에 있는 것인데, 말그래도 턴에 따라 체스말을 이동시키는 기능을 합니다. 이때, 가장 중요한 것은 whitePlayer.isFinish()입니다. whitePlayer는 Player 객체이며, State 객체를 가지고 있습니다. 그리고 이 State는 다시 isFinished 또는 Running를 의미합니다. 따라서, Player에게 "너는 어떤 상태니"라고 물어볼 수 있게 되니 더욱 객체지향적인 코드라고 할 수 있겠습니다.

 

 

이동 판단 여부는 제 코드를 많이 재사용했습니다. 특히, 해당 경로가 직선인지, 그리고 해당 말이 퀸, 룩, 비숍, 폰 중에 하나인지 확인하는 것이 중요합니다. 왜냐하면, 이 외의 체스말에 대해서는 자신이 갖고 있는 이동 전략을 단순히 확인만 하면 되기 때문이죠.

 

하지만, 퀸, 룩, 비숍, 폰은 특정 방향으로 1칸이 아니고 여러 칸을 갈 수 있으므로 필연적으로 중간 경로에 다른 기물이 있는지 확인해야 합니다. 또한, source부터 target까지의 경로가 일직선이어야 합니다. 나이트마냥 왼쪽으로갔다가 위쪽으로 가는 경로는 퀸, 룩, 비숍, 폰에 해당하지 않습니다.

 

 

    public boolean checkPath(final Source source, final Target target) {
        final List<Position> paths = initializePaths(source, target, chessBoard.get(source.position()));
        if (paths.isEmpty()) {
            return chessBoard.get(source.position()).canMove(target);
        }
        if (hasNoPiecesInPath(paths)) {
            return canPieceMoveToTarget(source.position(), target, paths);
        }
        return false;
    }

 

 

과거의 제 포스팅에서도 설명했던 부분인데, 검프의 코드 덕분에 더욱 인자가 간결해진 것을 아실 수 있습니다.

 

 

그 외에, 더 이상 보드가 체스말을 이동하는 것에 관여하지않으므로 이동 로직은 ChessGame로 이동하였습니다.

 

 

    private void move(final Position sourcePosition, final Position targetPosition, Player player) {
        Source source = new Source(
                player.findPiece(sourcePosition).orElseThrow(() -> new IllegalArgumentException("본인 턴에 맞는 기물을 선택해 주세요.")));
        Target target = new Target(chessBoard.findPiece(targetPosition));

        if (chessBoard.checkPath(source, target)) {
            player.move(source, target);
            Player anotherPlayer = anotherPlayer(player);
            anotherPlayer.toRunningState(player.state());
            checkPieces(anotherPlayer.state(), target);
            return;
        }
        throw new IllegalArgumentException("해당 위치로 이동할 수 없습니다.");
    }

 

 

이렇게 오전 시간동안 꽤 의미있는 기능을 구현하여 뿌듯했습니다.

 

 

오후 타임에는 점수 계산은 일부만 검프가 구현해 주고, 일단 급하게 스파크 자바로 넘어갔습니다. 저는 체스 게임을 하는 방 없이 단순히 하나의 게임만 돌아가게 만들었기 때문에 여러 개의 방을 구현한 검프의 코드를 사용하기로 합의했습니다. 다만, 서로의 js 코드를 합치는 과정에서 어려움을 겪었습니다. 왜냐하면, 각 체스말의 이미지 이름을 정하는 방법과 api 통신 방법이 상이하였기 때문이죠. 그리고 결정적으로 js 내에서 로직의 문제가 발생하여 대규모 리팩토링이 불가피했습니다.

 

다행히, 검프가 방을 만들고, 방에 들어가서 체스판을 띄워서 게임을 진행하는 것은 성공적으로 해 주었습니다. 다만, 나중에 가서는 게임 도중 종료 버튼을 눌렀을 때 먹통이 되는 문제와 체스말을 움직이다가 뜬금없이 TypeError가 나는 문제가 생겼습니다. 이때, 검프의 통신 방법은 axios였고, 저는 fetch였기에 제가 그나마 알고 있는 fetch로 바꿔서 에러를 해결했습니다.

 

후자의 문제는 fetch로 바꾸는 해결되었지만 (무엇이 원인이었는지는 모르겠습니다.), 전자의 문제는 여전히 남아있었습니다. 

 

 

let isEnd = false;

end.addEventListener("click", () => {
    console.log(isEnd);
    if (isEnd === true) {
        alert("이미 게임끝냤슈!");
        return
    }
    if (window.confirm("정말 끝내려구?")) {
        isEnd = true;

        const data = {
            chessName: localStorage.getItem("name"),
            isGameOver: isEnd
        };

        const option = {
            method: 'PUT',
            header: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(data)
        };

        fetch(basePath + "/games", option)
        .then(() => {
            loadGame();
        });
        alert("이 게임 끝났습니다.");
    }
});

 

 

미리 end 버튼 요소를 얻어낸 뒤에 이벤트 리스너를 연결하는 함수입니다. 이때, isEnd가 전역 변수로 되어 있으므로 이벤트 리스너 내에서 좋은 방법은 아니지만 이 전역 변수의 값을 바꿀 수 있겠다고 이해했습니다. 하지만, 막상 console.log() 찍어보면서 디버깅을 해 보니까, 이벤트 리스너 내에서 isEnd는 true로 바뀌지만, 근본적인 isEnd는 여전히 false로 유지되었습니다.

 

별의별짓을 하였지만 결국 해결을 못 하는 바람에 end 버튼에 클래스를 만들어 주었습니다. 현재 게임중이라면 run, 게임이 종료된다면 game_over로 설정하는 것이죠. 이를 통해, 가까스로 게임 종료 기능을 구현할 수 있었습니다.

 

 

정리

오늘도 여전히 스프링은 손도 못 대고 체스 코드를 합치는 데 전념했습니다. 그래도 점수 기능과 턴 확인 정도 및 검프 DAO, DTO 코드 이해 정도만 남아서 내일은 스프링을 달릴 수 있을 듯합니다.

 

서둘러서 미션 1단계를 마치고 2단계를 진행해야겠습니다.

댓글

추천 글