반응형

코딩텍스트, 개발자들을 위한 코딩테스트 블로그 서비스

 

그 중 AI가 어디에서 사용되었는지, 그리고 어떻게 AI 서비스를 선정하게 되었고 AI를 사용하는지를 알려주려고 한다.

 

가장 처음은 우리 코딩텍스트 프로젝트에서 AI가 어떻게 사용되었는지 확인해보자.

 

 

사용자에게 난이도, 알고리즘, 제작 시 고려할 사항을 받아 사용자 맞춤 코딩테스트 문제를 제작하는 서비스를 제공한다.

원하는 난이도, 알고리즘을 선택할 수 있고, 원하는 스토리텔링 혹은 원하는 기술을 고려할 사항으로 추가할 수 있다.

사용자 입맛대로 만들어진 코딩테스트 문제를 풀수 있도록 문제를 제공해주는 서비스다.

 

채팅을 쳐보아요

 

코딩테스트 풀이 중, 사용자가 원하는 질문을 외부로 나가지 않고 서비스 내부에서 질문할 수 있도록 한다.

직접 작성한 코드를 복사하지 않고 추가하여 질문하도록 기능해, 편의성을 높였다.

 

사용자 정보를 질문과 동시에 GPT에 제공할 수 있도록 로직처리하여 사용자가 질문하는 것에 대한 의도를 파악하여 GPT로 하여금 더 정확한 대답을 할 수 있도록 만든다.

 

문제를 풂과 동시에 궁금한 점을 해소할 수 있다는 점이 장점이 되겠다.

 

로직처리에 대한 것은 추후 제작될 링크를 통해 확인해주면 되겠다.

반응형
반응형

스웨거란?

Swagger : Open Api Specification(OAS)를 위한 프레임 워크

FrontEnd와 BackEnd간 API 명세를 위해 사용한다고 생각하면 되며, 사용 시 페이지 하나에 기록되어있다.

하나의 협업 툴이라고 생각하면 되며, 기존 프로젝트는 Notion을 사용했었는데, 이번에는 Swagger를 채택하여 사용해보기로 했다.

의존성 추가

build.gradle 파일에 해당 dependency를 추가한다.

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'

 

필자의 경우 Could not find org.springdoc:springdoc-openai-starter-webmvc-ui:2.2.0.Required by root project와 같은 에러메세지를 당도하여, 곤혹을 치뤘는데

 

build.gradle repositories에 해당 maven을 추가해준다.

maven { url 'https://repo.spring.io/snapshot' }

 

2018년도까지 업데이트 되었던 springfox를 사용하여 swagger를 작성했지만, 이제는 spring-openapi 라이브러리를 불러와 swagger를 작성한다. (2020년도에 springfox가 업데이트 되어 병행하는 경우도 있다.)

공식문서는 https://springdoc.org를 참조하자.

 

application.yml

springdoc:
  packages-to-scan: {스캔할 경로}
  default-consumes-media-type: application/json;charset=UTF-8
  default-produces-media-type: application/json;charset=UTF-8
  swagger-ui:
    path: /
    disable-swagger-default-url: true
    display-request-duration: true
    operations-sorter: alpha

application.yml에 해당 코드를 추가한다.

pakages-to-scan에 해당하는 경로를 넣게되면 해당 경로를 상위폴더로 두고 하위폴더를 전부 탐색하니 생각하여 경로를 설정하도록 하자. 필자는 프로젝트 전체를 경로로 넣었다.

 

API가 RESTFUL하게 통신을 주고받기 때문에 json타입으로 기본 설정한다.

나머지는 기본 url을 사용할건지 등의 기본 설정이기에 해당 설정을 따른다.

 

Config 파일 추가

Bean에 등록하여 서버 실행 시 참조에 오류가 없도록한다.

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.models.OpenAPI;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@OpenAPIDefinition(
        info = @Info(
                title = "LLM Service API",
                version = "1.0",
                description = "LLM Service API")
)
@Configuration
public class SwaggerConfig {
    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .info(new io.swagger.v3.oas.models.info.Info()
                        .title("LLM Service API")
                        .version("1.0")
                        .description("LLM Service API"));
    }
}

@OpenAPIDefinition 어노테이션과 OpenAPI를 정의하여 Swagger의 제목과 버젼 설명을 명세한다.

 

이후 각 Controller, DTO, Entity, ResponseEntity(디폴트 응답 틀)에 설명을 추가한다.

 

Controller

@RestController
@RequiredArgsConstructor
@RequestMapping("/llm")
@Tag(name = "Chat-GPT(LLM) 서비스", description = "LLM 관련 API")
public class LLMController {
    private final LLMService llmService;
    @Operation(summary = "코딩테스트 문제 생성 API", description = "난이도(1~5), 알고리즘, 추가 사항을 기반으로 코딩 테스트를 생성하는 API")
    @PostMapping("/code-generating")
    public ResponseEntity<LLMResponseDTO.CodeGenerateResponse> codeGenerator(@RequestBody LLMRequestDTO.codeGeneratingInfo request) {
        return llmService.codeGenerator(request);
    }

    @PostMapping("/chat")
    public ResponseEntity<LLMResponseDTO> chat(@RequestBody LLMRequestDTO.chatMessage request) {
        return llmService.chat(request);
    }
}

 

DTO

@Builder
@Getter
@AllArgsConstructor
@Schema(name = "LLMResponseDTO", description = "LLM 응답 양식", title = "LLMResponseDTO [LLM 응답 양식]")
public class LLMResponseDTO {
    @Builder
    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    @Schema(name = "CodeGenerateResponse", description = "코딩테스트 생성 응답", title = "CodeGenerateResponse [코딩테스트 생성 응답]")
    public static class CodeGenerateResponse{
        @Schema(name = "title", description = "문제 제목", example = "혼자 남지 않기 위한 최적의 경로 찾기")
        private String title;
        @Schema(name = "algorithm", description = "알고리즘", example = "DP")
        private String algorithm;
        @Schema(name = "content", description = "문제 내용", example = "<h3>혼자 남지 않기 위한 최적의 경로 찾기</h3>\n\n<p>이 문제는 '혼자 남지 않기 위한 최적의 경로 찾기'를 목표로 합니다. 주어진 2차원 격자에서 특정 조건을 만족하며 목적지에 도달하기 위한 행동을 결정해야 합니다. 주어진 조건을 만족하는 함수를 작성하고, 격자를 탐색하는 중 최대한의 안전성을 확보해야 합니다.</p>\n\n<h4>문제 설명:</h4>\n<p>이 문제는 특정 시작점에서 목적지까지 '혼자 남지 않기' 위한 최적의 경로를 찾아야 합니다. 2차원 격자에서는 특정 위치로 이동할 때 1의 스탭이 소요되며, 각 위치에서 움직일 수 있는 최대 경로 수를 제한하여 주어질 것입니다. 주어진 조건하에서 최단 경로를 결정해야 하며, 경로에 존재하는 위험 요소를 피하면서 격자를 탐색해야 합니다.</p>\n\n<h4>문제 제한 사항:</h4>\n<ul>\n<li>격자 크기: M x N (1 ≤ M, N ≤ 100)</li>\n<li>시작점: 출발 위치는 (0,0)으로 고정되어 있습니다.</li>\n<li>목적지: 목적지는 항상 오른쪽 아래 모서리 (M-1, N-1)입니다.</li>\n<li>위험 요소는 격자 내 임의의 위치에 존재할 수 있으며, 이를 피하는 것이 중요합니다.</li>\n</ul>\n\n<h4>입력 예시:</h4>\n<pre><code>\n5 5\n0 0 0 0 0\n0 -1 0 -1 0\n0 0 0 -1 0\n0 -1 -1 0 -1\n0 0 0 0 0\n</code></pre>\n\n<h4>출력 예시:</h4>\n<pre><code>\n7\n</code></pre>\n\n<h4>입출력 예 설명:</h4>\n<p>주어진 격자에서 (0,0)에서 (4,4)까지 가장 적은 스탭으로 이동하기 위한 경로는 7스탭 입니다. 중간에 -1은 이동할 수 없는 위치이며, 다른 안전한 경로를 찾아야 합니다.</p>")
        private String content;
        @Schema(name = "difficulty", description = "난이도", example = "3")
        private String difficulty;
        @Schema(name = "additionalNotes", description = "추가 사항", example = "문제의 조건을 만족하는 최소의 시간 복잡도를 갖는 알고리즘을 작성해줘.(can be null)")
        private String additionalNotes;
        @Schema(name = "testCases", description = "테스트 케이스 목록(front 필요 없을 확률이 높음.)")
        private List<TestCaseSpec> testCases;

        @Builder
        @Getter
        @AllArgsConstructor
        @NoArgsConstructor
        @Schema(name = "TestCaseSpec", description = "테스트 케이스 명세", title = "TestCaseSpec [테스트 케이스 명세]")
        public static class TestCaseSpec {
            @JsonProperty("input")
            @Schema(name = "input", description = "입력값", example = "5 5\n0 0 0 0 0\n0 -1 0 -1 0\n0 0 0 -1 0\n0 -1 -1 0 -1\n0 0 0 0 0")
            private String input;
            @Schema(name = "expectedOutput", description = "예상 출력값", example = "7\n")
            @JsonProperty("expected_output")
            private String expectedOutput;
        }


        public static LLMResponseDTO.CodeGenerateResponse of(String jsonResponse) {
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                JsonNode rootNode = objectMapper.readTree(jsonResponse);

                JsonNode problemInfoNode = rootNode.path("problem_info").get(0);

                List<TestCaseSpec> testCases = objectMapper.readValue(
                        rootNode.path("test_cases").toString(),
                        objectMapper.getTypeFactory().constructCollectionType(List.class, TestCaseSpec.class)
                );
                return LLMResponseDTO.CodeGenerateResponse.builder()
                        .title(rootNode.path("title").asText())
                        .content(rootNode.path("content").asText())
                        .algorithm(problemInfoNode.path("algorithm").asText())
                        .difficulty(problemInfoNode.path("difficulty").asText())
                        .additionalNotes(problemInfoNode.path("additional_notes").asText())
                        .testCases(testCases)
                        .build();
            } catch (Exception e) {
                throw new RuntimeException("Failed to parse JSON response"+ e.getMessage(), e);
            }
    }

 

ResponseEntity

@Data
@AllArgsConstructor
@Schema(name = "ResponseEntity", description = "전체 API 응답", title = "ResponseEntity [전체 API 응답]")
public class ResponseEntity<T> {
    @Schema(name = "statusCode", description = "응답 코드", example = "200")
    private Integer statusCode;
    @Schema(name = "message", description = "응답 메시지", example = "OK")
    private String message;
    @Schema(name = "data", description = "응답 데이터")
    private T data;

    public static <T> ResponseEntity<T> success(T data) {
        return new ResponseEntity<>(HttpStatus.OK.value(), "OK", data);
    }

    public static <T> ResponseEntity<T> error(Integer statusCode, String message, T data) {
        return new ResponseEntity<>(statusCode, message, data);
    }
}

 

이후 서버를 실행시킨 뒤, localhost:8080/swagger-ui/index.html# 로 접속한다.

 

메인 페이지

 

상세 페이지 Request
Response

 

이렇게 정의하여, 프론트에게 API 명세를 껴안아 주면 그때부터 완전한 프로젝트 협업의 시작이 되겠다.

 

반응형
반응형

먼저 필자는 코딩테스트 블로그를 만들고, 코딩테스트와 관련된 내용을 chatGPT에 질문하고, 코딩테스트 문제를 생성하는 서비스를 만드려고 한다.

사용하는 유저 혹은 문제 마다 다 다른 대화내용일텐데,
chatGPT를 사용하는 것처럼 질의응답 내용을 다 파악하여 답할 수 있을까?

 

결론은 그렇지 않다.

 

해당 내용을 기억하지 못하기 떄문에, 기존 대화 내용을 프롬프트에 담아 제공을 해준다.

이를 통해 chatGPT로 하여금 이전 내용을 숙지하여 의도에 맞는 답을 전달해줄 수 있도록 할 수 있는것이다. (물론 토큰 비용은 더 나가겠지만.. 좋은 서비스를 제공하기 위해서는 감안해야 할 듯 하다.)


따라서 나는 경우를 세가지로 나누게 되었다.

  1. 신규 사용자의 첫 질문
  2. 기존 사용자의 첫 코딩테스트 질문
  3. 기존 사용자의 연속된 코딩테스트 질문

경우마다 다 다르게 나누어 프롬프트를 설정한다.

프롬프트는 다음과 같다.

  1. 해당 언어를 주력 기술로 가지고 있는 사용자
  2. 기존 대화에서 현재 질문한 것과 가장 유사한 질문 및 가장 유사하지 않은 질문을 제시한다.
  3. 캐시 메모리를 이용하여, 가장 최근 두 질의응답을 저장해 이를 반환한다.

그렇다면 해당 행동은 RAG라고 볼 수 있을까?

RAG(Retrieval-Augmented Generation)는 외부의 정보를 검색하거나 불러와서 생성 모델의 입력으로 제공하는 방식으로, 본질적으로 검색(검색 및 불러오기) + 생성 접근 방식을 의미한다.

 

유의미한 결과를 도출하기 위해 정보를 다듬어 다시 제공한다는 점에서 RAG라고 볼 수 있다. 물론 주로 사용되고 있는 기술이 외부의 정보를 한데 모아 정보를 통합하여 질문하는 방식이다. 허나 유창한 기술을 사용하지 않더라도 (원칙에 부합하기도 하고) 사용자 입장에서 더 좋은 결과를 획득할 수 있다면 그것도 올바른 방법이지 않을까 필자는 생각한다. 

 

물론 2번 프롬프트에서 BERT모델을 사용하여 벡터화 한 값을 통해 유사한 질문과 가장 유사하지 않은 질문을 제시하기는 한다.

그렇다면 RAG를 어떻게 사용하게 되는지 확인해 보자.

 

  1. Retrieval (검색 및 불러오기)
    • 사용자가 이전에 했던 질문과 답변, 문제 정보 등을 DB(2번 프롬프트)나 캐시(3번 프롬프트)에서 불러온다.
    • 필요한 경우 중요한 부분만 요약하여 정보를 효율적으로 구성한다.(2번 프롬프트)
  2. Augmentation(정보 강화)
    • 불러온 대화 이력이나 문제와 관련된 정보를 프롬프트에 추가하여, 모델이 이전 대화 맥락을 인식할 수 있게 한다.
    • 요약된 이력, 사용자 성향, 특정 질문 패턴 등이 프롬프트에 포함되므로 사용자 맞춤형 답변을 생성하도록 도와준다.
  3. Generation(응답 생성)
    • 최종적으로 강화된 프롬프트를 모델에 입력하여 답변을 생성한다.
    • 이때, 모델은 전달된 프롬프트에 포함된 정보를 참고하여 맥락에 맞는 답변을 하게된다.

이렇게 발전에 흐름에 전지전능하다고 생각되는 GPT도 다 해주는 건 아니기에, 해당내용을 참고하여 API를 잘 다뤄보기 바란다.

 

 

반응형
반응형

Azure OpenAI가 반응속도가 느리고, 잘 작동하지 않아 유료지만, 일반 OpenAI chatGPT를 사용할 계획이다.

chatGPT playground에서 간단하게 설정해보자.

함수 설정, generate(beta)로 설정하기

generate에 지시사항을 넣고 실행하면 함수에 대한 설정이 가능하다. json형태로 제시되니 읽어보고 수정하면 된다.

함수가 추가된 모습

 

예시 설정

Assistant는 User의 질문에 대한 답변을 제시한다. 이를 원하는 형식으로 알맞게 조정할 수 있다.

 

 

반응형
반응형

Redis란?

먼저 Redis를 사용하는 이유에 대해서 알아야한다.

 

Redis는 캐시메모리에 최적화 되어있고, 반응속도를 빠르게 하는데 일가견이 있다.

벡터화된 데이터를 제공하기도 하여, 유사도 검색기능까지 구현할 수 있다.

 

이 외에도 제공하는 기능이 많으므로 알아보고 사용하면 되겠다.

 

Redis 설치 작업

homebrew로 redis 다운로드

brew install redis

위 명령어를 terminal에 입력한다. homebrew가 다운로드 되어 있어야 한다.

 

레디스 실행

brew services start redis

설치한 레디스를 실행시킨다.

레디스 클라이언트 접속 후 ping 명령어 입력

redis-cli
ping

redis-cli 명령어를 통해 레디스에 접속한 뒤, 켜져있는지 확인하기 위해 ping 명령어를 입력한다.

PONG이라는 답이 오면 잘 켜져있음을 확인할 수 있다.

 

SpringBoot와 연동

이후 스프링부트와 연동하도록 한다.

gradle을 주로 사용하기에 해당 dependency를 추가한다.

build.gradle에 추가.

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}

 

이후 RedisConfig파일을 작성한다.

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        return template;
    }
}

Spring Boot에서 Redis와 상호작용하기 위해 RedisTemplate 또는 비동기 처리를 위한 ReactiveRedisTemplate을 사용할 수 있게 해준다.

반응형
반응형

Azure에서는 학생 인증을 진행 할 경우 100달러를 무료 지급하는 서비스를 진행하고 있다.

OpenAI를 이용한 API를 만들어보고 싶거나, 서비스를 맛보고 싶은 경우 진행하도록 하자.

채팅 플레이 그라운드 - chatGPT에 대한 설정을 진행할 수 있다.

MarketPlace에서 OpenAI를 검색하여 리소스를 생성하고 생성된 리소스에 들어온다.
이후 개요 탭에서 Azure OpenAI studio 이동을 누르면 해당 사진인 채팅 플레이 그라운드에 접속할 수 있게 된다.

해당 채팅 플레이 그라운드에서 지침 및 컨텍스트 제공을 통해 클라이언트에 답할 양식등을 지정할 수 있다.

 

배포를 해보도록 하자.

기본 모델과 미세 조정된 모델을 선택

배포 시, 기본 모델과 미세 조정된 모델을 선택할 수 있는데 나는 chatGPT의 기본모델을 선택하기로 하겠다.

제공하는 chatGPT 모델 종류

여기서 모델을 설정할 수 있다. 나는 가장 무난한 gpt-4를 사용하기로 했다.

모델 배포 상세 페이지

gpt-4 모델을 선택해서 배포유형에 따라 선택할 수 있으며, 리소스 위치 및 모델 버전 또한 사용자 지정으로 선택할 수 있다.

 

배포 유형은 총 네가지이다.

 

1. 글로벌 표준(Global Standard):

 전 세계적으로 서비스가 필요한 경우 선택하는 배포 유형입니다.

 다양한 지역에 걸쳐 안정적인 성능을 제공해야 할 때 유용합니다.

 글로벌하게 빠르고 일관된 응답 시간을 보장해야 하는 서비스, 예를 들어 전 세계적인 사용자 기반을 가진 서비스에 적합합니다.

2. 표준(Standard):

 특정 지역 또는 로컬 서비스에 적합한 배포 유형입니다.

 주로 지역적인 사용자 대상이거나 글로벌 성능이 필요 없는 경우 선택합니다.

 적은 비용으로 적절한 성능을 제공할 수 있습니다.

3. 전체 일괄 처리(Batch Processing):

 데이터를 한꺼번에 처리하는 작업에 적합한 배포 유형입니다.

 실시간 처리보다는 대규모 데이터를 일괄적으로 처리하는 것이 필요한 경우에 유용합니다. 예를 들어, 매일 밤 대량의 데이터를 처리하거나 로그 분석에 사용됩니다.

 실시간 성능보다는 작업 완료 시간이 중요한 경우에 선택합니다.

4. 프로비저닝 된 관리(Provisioned Management):

 사전에 리소스를 할당하고 관리하는 배포 유형입니다.

 특정 트래픽 패턴이 예측 가능하거나 고정된 리소스 할당이 필요한 경우 적합합니다.

 리소스 사용량을 미리 예상하고 설정할 수 있어, 필요에 따라 성능을 관리할 수 있습니다.

모델 배포 사용자 지정

 

이 중 한국에서 서비스할 예정이므로 표준을 선택하는 것이 일반적이다. 그래서 표준을 선택했으나, 남은 리소스가 오스트레일리아 혹은 캐나다 등과 같이 한국과 먼쪽에 위치하고 있으므로, 글로벌 표준으로 사용 하기로 한다.

 

글로벌 표준을 통한 chatGPT 모델 생성은 다음 포스팅에 기록하겠다.

반응형

'BackEnd > OpenAI chatGPT' 카테고리의 다른 글

chatGPT API를 사용할 때 유의할 점 (RAG)  (12) 2024.11.03
OpenAI chatGPT playground  (0) 2024.11.03
반응형

https://youtu.be/qkTtmgCjHhM

코딩애플님의 간단한 javaScript 게임개발 예제를 따라 작성해보았다.

게임개발 2까지 진도를 나가서, 이중점프 안되게 뭐 다 코드를 작성했다.

드래그도 안되게 했고, 스페이스바를 눌렀을 때 홈페이지 내부에서 동작하지 않도록 처리도 했다.

 

let $canvas = document.getElementById('canvas');
let $h1 = document.querySelector('h1');
let ctx = $canvas.getContext('2d');
let $pScore = document.getElementById('score');
let $pHScore = document.getElementById('Hscore');

// 드래그 방지
document.onselectstart= () => {return false};
// // space바로 page down 방지 (keyborad 입력 금지)
// document.onkeydown= () => {return false};

$canvas.width = window.innerWidth - 100;
$canvas.height = window.innerHeight - 100;
$h1.style.display = 'none';
$h1.style.position = 'absolute';
// $pHScore.style.float = "right";
// $pScore.style.float = "right";

let line = {
    draw(){
        ctx.moveTo(-20,450);
        ctx.lineTo(2000,450);
        ctx.lineWidth = 4;
        ctx.stroke();
    }
}

let me = {
    x : 40,
    y : 400,
    width : 50,
    height : 50,
    
    draw(){
        ctx.fillStyle = 'rgb(0,200,0)';
        ctx.fillRect(this.x, this.y, this.width, this.height);
    }
}

let jumpNow = false;
let jumping = false;
let standing = false;
let jumpTimer = 0;
let jumpSpeed = 3;
// 나의 점프

//
let jumpKey = ['Space', 'ArrowUp', 'KeyW'];
let standKey = ['ArrowDown', 'KeyS']
document.addEventListener("keydown", (e) => {
    if(jumpKey.includes(e.code)){
        if(jumpNow === false){
            jumping = true;
            standing = false;
        } 

        if(game == false){
            game = true;
            location.reload();
        }
    }
    
    else if(standKey.includes(e.code)){
        standing = true;
     
    }
}) 

class Enemy {
    constructor(){
        this.x = 1600;
        this.y = 400;
        this.width = 50;
        this.height = 50;
    }
    draw(){
        ctx.fillStyle = 'rgb(200,0,0)';
        ctx.fillRect(this.x, this.y, this.width, this.height);
    }
}

let $input = document.querySelector('input');

let score = 0;
if(localStorage.getItem('highScore') === null){
    localStorage.setItem('highScore', 0);
}
let highScore = localStorage.getItem('highScore');

let game = true;
let timer = 0;
let enemies = [];

// 빈도 수
const hard = 140;
const normal = 240;
const difficulty = [hard, normal];
const choice = 0;

// 속도

// 충돌 여부
function isCrush(me, enemy){
    let xDif;
    let yDif = enemy.y - (me.y + me.height);
    
    if(enemy.x > 40){
        xDif = enemy.x - (me.x + me.width);
    }
    
    else{
        xDif = me.x - (enemy.x + enemy.width);
    }
    
    
    if(xDif < 0 && yDif <0){
        cancelAnimationFrame(animation);
        ctx.clearRect(0,0, $canvas.width, $canvas.height);

        game = false;
        // $h1.style.position = 'position:relative';
        // $h1.style.display = 'block';
        
        //최고기록 갱신
        if(score > highScore){
            highScore = score;
            localStorage.setItem('highScore', highScore);
        }  
    }
}

function excuteEveryFrame(){
    animation = requestAnimationFrame(excuteEveryFrame);
    timer++;
    score++;
    
    ctx.clearRect(0,0, $canvas.width, $canvas.height);

    // 적 생성
    if(timer % difficulty[1] == 0){
        let enemy = new Enemy();
        enemies.push(enemy);
    }

    // 적 소환 및 제거
    enemies.forEach((e, i, o) =>{
        if(e.x < -40) {
            o.splice(i, 1);
        }

        isCrush(me, e);

        e.draw();
        e.x -= 3;
    })

    if(jumping === true && jumpNow === false){
        me.y-= (jumpSpeed);
        jumpTimer++;
        
    }

    if(jumpTimer > 40 || standing == true){
        jumping = false;
        jumpTimer = 0;
        jumpNow = true;
    }

    if(jumping === false){
        if(me.y <= 400){
            me.y+= (jumpSpeed+0.3);

            if(standing === true){
                me.y += (jumpSpeed + 1);
            }
            // 오차 방지 및 중복 점프 방지
            if(me.y > 398){ 
                me.y = 400;
                jumpNow = false;
            }
        }
    
    }

    // 선, 자신 생성
    me.draw();
    line.draw();
    $pScore.innerHTML = 'score : ' + score;
    $pHScore.innerHTML = 'highscore : ' + highScore;

    // 최고기록 실시간 갱신
    if(score > highScore){
        $pHScore.innerHTML = 'highscore : ' + score;
    }

}

excuteEveryFrame();

 

코드해석은 추후 수정하도록 하겠다!

궁금한 부분은 질문 바란다.

반응형
반응형

함수 중복 제거하기

document.querySelector('#num-0').addEventListener('click', onClickNumber('0'));
document.querySelector('#num-1').addEventListener('click', onClickNumber('1'));
document.querySelector('#num-2').addEventListener('click', onClickNumber('2'));
document.querySelector('#num-3').addEventListener('click', onClickNumber('3'));
document.querySelector('#num-4').addEventListener('click', onClickNumber('4'));
document.querySelector('#num-5').addEventListener('click', onClickNumber('5'));
document.querySelector('#num-6').addEventListener('click', onClickNumber('6'));
document.querySelector('#num-7').addEventListener('click', onClickNumber('7'));
document.querySelector('#num-8').addEventListener('click', onClickNumber('8'));
document.querySelector('#num-9').addEventListener('click', onClickNumber('9'));

위와 같은 수식이 있다고 했을 때, 같은 로직이 반복되니 onClickNumber()이라는 함수로 묶어 중복을 제거했다.

const onCLickNumber = (number) => {
	if(operater) {
		numTwo += number;
	} else {
		numOne += number;
	}
	$result.value += number;
	}

위와 같은 수식이라면 에러가 나게된다. 왜? addEventListener에서 사용되는 콜백함수이므로 return이 무엇인지 살펴보아야 하는데, onCLickNumber()의 return값은 undefined이므로 결국 아래와 같아진다.

addEventListener('click', undefined);

그렇다는 건 이때 return에 함수를 주기위해서 고차함수를 사용해야하는데 다음과 같이 고쳐주면 되겠다.

const onCLickNumber = (number) => () => {
	if(operater) {
		numTwo += number;
	} else {
		numOne += number;
	}
	$result.value += number;
	}
// 당연히 인자가 앞에 와야겠지? 생각 잘하자.

간단하다.

+) 코드 정리 방법 - if문

return을 해버리면, 함수 자체가 끝나게 되므로, if else문을 사용하지 않고도 else의 효과를 낼 수 있다.

 

배열 - forEach, map, fill 알아보기

Array(9) // 배열의 길이가 9인 배열 생성 이건 javascript에서만 가능해용 이거 함수네..
Array(9).fill(0) // (9) [0, 0, 0, 0, 0, 0, 0, 0, 0]

fill은 보면 알겠지만 채워준다.

const answer = [5, 6, 3, 2];
const value = '4652'
let strike = 0;
let ball = 0;

answer.forEach((element, index) => {
	const answerIndex = value.indexOf(element);	
	if(answerIndex === index) strike++;
}} 
console.log(strike+"개 맞췄습니다")
//return 2개 맞췄습니다.

forEach는 for문 도는 거 처럼 행동 닉값 함 ㅇㅇ, element와 index를 순서로 매개변수를 받을 수 있다. 배열의 처음부터 끝까지 순회하며, 콜백함수 형식으로 사용해야 한다.

const array = [5, 6, 3, 2];
const result = array.map((element, index) => {
	return element * 2;
})
//return undefined, result (4) [10, 12, 6, 4]

map함수도 마찬가지로 element와 index를 순서로 매개변수를 받고, 배열의 처음부터 끝까지 순회하며 콜백함수 형식으로 사용해야하는 것은 같지만, return을 주어 그 값을 배열에 mapping 할 수 있다.

const array = Array(9).fill(1).map((el,idx) => idx +1)
array.splice(2,3) // index가 2인곳 부터 3개 빼온다.
//return (3) [3, 4, 5], array (6) [1, 2, 6, 7, 8, 9]
let a = array.slice(4)
//return (2) 8, 9
let b = array.slice(4,5)
//return 8, slice(startIndex, EndIndex-1) 

이런식으로 응용할 수 있겠다.

Array 함수를 통해 길이가 9이고, 1~9까지의 값을 가지는 array를 선언했다.

splice 함수를 통해 index가 2인곳 부터 3개를 반환했고

slice 함수를 통해 a에 인덱스가 4인곳 부터 따로 배열을 저장했으며

slice 함수를 통해 b에 인덱스가 4인곳 부터 5-1인곳 까지 배열을 저장했다.

 

조그마한 정리

  1. 배열 정렬은 그냥 검색해라.
  2. setTimeout( 콜백함수, 시간(밀리초))
  3. 스코프 - 범위 : var은 함수 스코프, let은 블록 스코프

 

이벤트 관리 EventListener

태그.addEventListener('click', 고차함수())); // 마우스 버튼 클릭 시 함수 행동
태그.removeEventlistener('click',고차함수())); // 변수 사용하는 것이 더 좋다
/*
mouseover 마우스를 태그 위에 올리면 발생
mouseout 마우스가 태그 바깥으로 벗어나면 발생
mousedown 마우스버튼을 누르고 떼기 전, 태그를 드래그할 떄 사용
mouseup 드래그한 태그를 드랍할 떄 사용
focus 태그에 포커스가 갔을 때 발생
blur 태그가 포커스에서 벗어났을 떄 발생
keypress 키를 누르는 순간 시작 누르고 있는 동안 계속 발생
keydown 키를 누를 때 발생
key up 키를 뗄 때 발생
load 페이지 상 모든 요소(파일)의 다운로드가 완료되었을 때 발생
resize 브라우저 창 크기 조절 시 발생
scroll 스크롤바 드래그 혹은 키보드(up, down)를 사용하거나 휠을 사용해서 웹페이지를 스크롤 할 때 발생
페이지에 스크롤바 없으면 이벤트 발생 안함.
unload 링크 클릭하여 다른페이지 이동 혹은 브라우저 탭 닫을 떄 브라우저 창을 닫을 때 이벤트 발생
change 상태가 변경되었을 때 발생
*/

위의 식은 잘못되었다. 왜냐면 함수와 함수간 비교는 객체와 객체간 비교로 볼 수 있는데, 이 상황에서 객체간 비교는 항상 false가 나온다. 객체의 호출은 재생성과 똑같다고 생각하면 되기에 그런데, 변수는 조금 다르다. 그렇기에 함수의 값을 변수에 담아 비교해보도록 하자. 그러면 같다고 할 수 있으니께롱

니 변수에 저장~

 

반응형

+ Recent posts