Snake클래스

jh Seo·2023년 7월 21일
0

Snake.h

#ifndef _Snake_H
#define _Snake_H
#include "IDraw.h"
#include<deque>
using namespace std;

class Snake : public IDraw
{
public:
    Snake();
    ~Snake();
    Snake(pair<int,int> startNode);
    pair<int,int> GetHead(){return curHead;}
    void SetHead(pair<int,int> tmpHead){curHead=tmpHead;}
    char GetDir(){return direction;}
    void Draw();
    void SetDirection(char tmp){direction=tmp;}
    void Move(char ch);
    void Shrink();
    void Extend();
    bool IsDead();
    bool IsSnakeBody(pair<int,int> coordinate);
    void Die();
    

private:
    deque<pair<int,int>> snakeBody;
    pair<int,int> curHead;
    pair<int,int> prevHead;
    char direction;
    pair<int,int> tmpTail;
    bool isDead;
};

#endif

멤버 변수로는
1. snake의 몸들의 좌표들을 가지고있는 deque
2. 현재머리좌표를 pair<int,int>형으로 가지는 curHead
3. 이전 머리 좌표를 가지고 있는 prevHead
4. direction을 switch로 사용하기 위해서 char형으로 구현을 해놨다.
5. 꼬리좌표인 tmpTail
6. 죽었는지 판단하기 위한 bool형 변수

Snake.cpp

#include "../Include/Snake.h"
#include <ncurses.h>
#include <algorithm>
using namespace std;

Snake::Snake(){
        snakeBody.clear();
    snakeBody.push_back({5,5});
    snakeBody.push_back({6,5});
    snakeBody.push_back({7,5 });
    curHead=make_pair(5,5);
    prevHead=make_pair(0,0);
    direction='U';
}

/// @brief StartNode가 뱀의 head가 되어서 head부터 오른쪽으로 3가지 노드로 구성
/// @param StartNode 
Snake::Snake(pair<int,int> StartNode){
  snakeBody.clear();
    snakeBody.push_back(StartNode);
    snakeBody.push_back((pair<int,int>){StartNode.first+1, StartNode.second});
    snakeBody.push_back((pair<int,int>){StartNode.first +2,StartNode.second});
    curHead=StartNode;
    //절대 startNode가 될수없는 좌표
    prevHead=make_pair(999999,99999);
    direction='U';
}
Snake::~Snake(){
    snakeBody.clear();
    deque<pair<int,int>>().swap(snakeBody);
}
void Snake::Draw()
{
    for (auto it = snakeBody.cbegin(); it != snakeBody.cend(); ++it)
    {
        mvprintw(it->first,it->second, "#");
    }
}
void Snake::Move(char ch){
    pair<int,int> tmpPoint;
    switch(ch){
        case 'U':
        tmpPoint=make_pair(curHead.first-1, curHead.second);
        direction='U';
        break;
        case 'L':
        tmpPoint=make_pair(curHead.first, curHead.second-1);
        direction='L';
        break;
        case 'R':
        tmpPoint=make_pair(curHead.first, curHead.second+1);
        direction='R';
        break;
        case 'D':
        tmpPoint=make_pair(curHead.first+1, curHead.second);
        direction='D';
        break;
        case 'X':
        Move(direction);
        return;
    }
    //뒤로가면 죽음
    if(tmpPoint==prevHead) Die();
    //매 움직임마다 충돌처리매니저의 map값 갱신시키는것보단 나아보임
    if(IsSnakeBody(tmpPoint)) Die();
    
    snakeBody.push_front(tmpPoint);
    tmpTail=snakeBody.back();
    snakeBody.pop_back();
    prevHead=curHead;
    curHead=tmpPoint;
}
void Snake::Die(){
    isDead=true;
}

void Snake::Shrink(){
    snakeBody.pop_back();
    //길이가 2미만이면 죽음
    if(snakeBody.size()<2) Die();
}
void Snake::Extend(){
    snakeBody.push_back(tmpTail);
}
bool Snake::IsDead(){
    return isDead;
}
bool Snake::IsSnakeBody(pair<int,int> coordinate){
    if(find(snakeBody.begin(),snakeBody.end(),coordinate)!=snakeBody.end()){
        return true;
    }
    return false;
}
  • 생성자로는 시작위치를 지정해주는 생성자와 미리지정한 위치인 5,5에서 시작하는 생성자가 있다.

  • 소멸자는 검색해봤더니 빈 deque와 swap함수를 해주면
    메모리를 해제한다고 봐서 저렇게 구현했다.

  • Idraw* 인터페이스를 상속받아 구현한 순수가상함수 Draw는
    deque에 들어있는 모든 좌표를 순회하며 '#'을 그린다.

  • 움직이는 함수인 move() 에선 char형을 인자로 받는다.
    L은 왼쪽, U는 위, D는 아래, R은 오른쪽이다.
    switch문을 통해 해당 방향으로 움직였을 때의 좌표를 tmpPoint에 저장한다.
    tmpPoint가 이전의 머리위치인 prevHead좌표이면 죽는다.
    tmpPoint가 현재 몸이 위치하는 곳이면 죽는다.
    죽는 위치가 아니라면 해당 좌표를 deque에 push_front를 하고
    tmpTail, curHead, prevhead를 갱신해준다.

  • Die()는 isdead true로 변경시켜서 죽는 함수고,
    shrink()는 deque의 pop_back연산이다.
    extend()는 꼬리좌표를 push_Back연산을 통해
    더 늘어나도록 만들었다.

  • IsSnakeBody()는 좌표값을 인자로받아
    algorithm헤더의 find함수를 사용해 deque의 원소인지 판별한다.

생각

처음에 snake의 모든좌표를 연결리스트를 통해
각 노드의 위치를 해당 노드의 다음 노드위치값으로 변환하는 형식으로 구현하려했다.

하지만 좀 더 고민해보니 그럴 필요가 없는게 모든 노드의 위치가 다 갱신되어도
사람들 눈에는 머리와 꼬리만 움직이는 것처럼 보인다.

따라서 front,back 전부 연산이 자유로운 deque을 통해 뱀의 몸통을 구현했고,
움직일때는 push_front(), pop_back()을 통해 좌표를 갱신한다.
길이가 줄어들때는 push_front(), pop_back(), pop_back()이런식으로 pop연산을 두번하면 길이가 줄어들며,
길이가 늘어날때는 push_front(),pop_back(),push_back()을 통해 길이가 늘어난다.

움직이는걸 이미 push, pop으로 함수를 짜버려서 변경하기 귀찮아서,
이전 꼬리 좌표를 저장해둔 후, 길이 늘어날때는 꼬리좌표를 push하는 방식으로 짜게 되었다.

또한 현재 충돌처리 매니저에서 모든 충돌체 정보를
map<좌표, 충돌체객체> 이렇게 관리를 하고 있다.
원래 snake의 좌표도 해당 map에 넣어 자기 몸에 머리가 닿는지 체크하고싶었다.

하지만 매 움직임마다 map정보를 갱신을 해줘야 하는 게 문제였다.
또한 제일 큰 문제는 collidable인터페이스에서 snake를 상속 및 헤더파일 참조하고 있고,
snake도 충돌처리매니저에서 관리하려면 collidable인터페이스를 상속 및 참조해야되서 상호참조가 발생한다.

설계를 잘못한 탓이 크다..
따라서 어쩔수 없이 snake의 좌표는 매 움직임마다 해당 좌표에 snake바디가있는지
find함수로 체크한다.

profile
코딩 창고!

0개의 댓글