본문 바로가기
카테고리 없음

스피드 연산 게임 만들기1

by 경자꿈사 2024. 7. 22.

지난 글에 이어 '객체와 클래스' 에 대한 얘기를 조금 더 할 게 남아 있긴 하지만 너무 문법적인 얘기만 계속하다 보면 어렵고 재미 없을 수도 있기에 이쯤에서 재밌는 프로그램을 하나 만들어 볼까 한다.

스피드 연산 게임을 만들어 볼건데 아마 누구나 한 번쯤 비슷한 게임을 해봤을 거 같고 혹시 초등학교 다니는 자녀가 있는 분들은 이 게임을 직접 만들어서 아이들과 함께 해보면 좋지 않을까 라는 생각에서 선택했다.

본격적으로 게임을 만들기 전에 몇 가지 생각해야 할 것들이 있다. 그 중 하나가 게임의 난위도인데 게임 시작 시 난위도를 직접 선택하는 기능이 있다면 좀 더 완성도 있는 게임이 될 거 같다. 그리고 게임 플레이어에게 보여지는 화면을 화려하게 꾸민다거나 음향 효과도 내주면 게임의 재미를 더할 수도 있을 것 같다.

그러나 여기서는 이러한 것들을 고려하지 않고 게임 플레이 자체에 집중하면서 코드를 작성해 보려고 한다. 최대한 쉽고 빠르게 내가 원하는 것을 직접 만들어 봄으로써 프로그래밍 자체에 대한 재미를 끌어올리는 게 목적이기 때문이다.

그러면 '스피드 연산 게임' 을 실제 플레이 할 때 어떻게 진행되는지 생각해보자.

랜덤하게 출제되는 문제가 화면에 보여지면 플레이어는 최대한 빠르게 암산하여 키보드로 답을 입력하고 엔터를 누르면 정답 여부가 화면에 나오고 이어서 다음 문제가 나오게 된다. 그렇게 정해진 시간이 지나면 최종 점수가 화면에 나오면서 게임은 종료된다.

이전 글들에서 몇 가지 프로그램을 만들어 보면서 사용자 키보드 입력에 대한 처리와 반복문을 작성하는 방법은 이미 알아봤고 랜덤하게 문제를 생성해야 하기 때문에 rand 메서드 역시 필요할텐데 이것 또한 여러 차례 사용해 봤다.

그렇다면 '스피드 연산 게임' 에서 가장 중요하다고 할 수 있는 '정해진 시간이 지나면 종료' 는 어떻게 만들 수 있을까?

Timeout 모듈의 timeout 메서드를 사용하면 간단히 해결된다. 아래 코드 실행 결과를 보면 timeout 메서드에 인수 1과 함께 블록을 주고 실행하니 블록 안의 첫 번째 코드에서 출력한 메시지만 나오고 마지막 메시지는 메시지 내용 그대로 화면에 출력되지 못하고 Timeout::Error 가 발생한 걸 볼 수 있다.

>> require 'timeout'
=> false
>>
?> Timeout.timeout(1) do
?>   puts "start..."
?>   sleep 2
?>   puts "이 메시지는 출력되지 않습니다"
>> end
start...

Timeout::Error (execution expired)

'timeout' 메서드에 인수로 준 1은 1초를 의미하고 이렇게 하면 블록 안의 내용이 다 실행되지 않았더라도 1초가 지나면 강제로 종료시키게 된다. 블록 안의 sleep 메서드는 인수로 건넨 시간(초) 동안 프로그램의 진행을 멈추게 해준다. 결국 타임아웃 시간은 1초인데 2초 동안 프로그램을 멈추게 했으므로 sleep 메서드 호출 다음의 코드는 실행될 수가 없는 것이다.

아래 처럼 코드를 작성해서 한 번 더 테스트해보자. 0.1초 후에 타임아웃되도록 작성했다.

require 'timeout'

begin
  Timeout.timeout(0.1) do
    i = 1
    while true
      puts i
      i += 1
    end
  end
rescue
end

실행할 때마다 출력되는 최댓값이 아마 다를텐데 이건 실행 시점의 컴퓨터 상황(CPU나 메모리 사용량 등)에 영향을 받을 수 있으므로 신경쓸 건 없다. 그 보다는 처음 irb에서 timeout 메서드를 테스트했을 때 보여지던 여러 줄의 에러 메시지가 보이지 않게 되었다. 그것은 timeout 메서드를 호출하는 코드를 예외 처리 구문(begin ... rescue ... end) 안에 작성했기 때문이다.

먼저 '예외 처리'에 대해 간단히 설명을 하자면 '예외' 란 말 그대로 일반적인 상황에서 벗어나는 경우를 말하는데 컴퓨터 프로그램 역시 예외적인 상황이 발생하여 의도와는 다르게 동작하는 경우가 발생할 수 있고 이러한 것에 미리 대비하여 코드를 작성하는 것이 '예외 처리' 이다. 예외적인 상황에는 프로그램 실행 중 컴퓨터 메모리나 디스크 공간 등이 부족해지거나 인터넷 연결이 갑자기 끊기는 등 자주 발생하진 않지만 만약 발생하면 대처가 어려운 것들도 있는 반면 사용자가 숫자를 입력해야 하는데 문자를 입력하는 경우와 같이 예상 및 대처가 쉬운 예외 상황들도 있다.

예외가 발생할 수 있는 코드를 begin 과 rescue 사이에 작성하고 실제 예외가 발생했을 때 처리해야 하는 일이 있다면 rescue 와 end 사이에 작성해 넣으면 된다.

그런데 timeout 메서드가 예외를 발생시킨 다는 것을 어떻게 알 수 있을까? 루비 문서에는 메서드에 대한 예제 코드와 함께 자세한 설명이 나오는데 루비 문서를 보는 세 가지 방법을 알아보자.

우선 가장 일반적인 방법은 루비 공식 문서 사이트를 이용하는 것이다. 웹브라우저를 열어 주소창에 https://ruby-doc.org 를 입력한 다음 현재 버전의 문서에 대한 링크를 누르고 좌측 상단의 검색 기능을 통해 원하는 것을 찾으면 된다.

아래 두 번째 그림을 보면 timeout 메서드에 대한 설명이 나오는 것을 볼 수 있는데 특히 두 번째 파라미터인 klass에 대한 설명 부분에 Timeout::Error 발생에 대한 내용이 나와 있다.

그 다음 방법으로는 컴퓨터에 루비를 설치할 때 함께 설치한 ri 툴을 이용하는 것이다. cmd 창을 열어 ri 명령과 함께 알고 싶은 클래스나 모듈 또는 메서드 이름을 입력 하면 되는데 내가 알고 싶은 메서드가 특정 클래스의 인스턴스에 대해 호출하는 메서드(인스턴스 메서드)라면 '클래스#메서드' 형식으로 입력하면 되고 그게 아니라 클래스나 모듈 자체에 대해 호출하는 메서드(클래스 메서드 또는 모듈 메서드)라면 '클래스.메서드' 또는 '모듈.메서드' 형식으로 입력하면 된다.

아래 그림을 보면 ri Hash#[] 는 Hash 객체에 대해 특정 키로 값을 조회하는 인스턴스 메서드 []에 대한 설명을 보여주고 ri Hash.[] 은 Hash 객체를 생성하는 클래스 메서드인 []을 설명하고 있다. 여기 설명대로 해시 객체를 new 메서드 말고 [] 메서드로도 생성 가능한지 irb 를 열어 직접 테스트해 보길 바란다.

우리가 사용할 Timeout 모듈의 모듈 메서드인 timeout에 대한 설명도 ri 명령으로 볼 수 있다.

마지막 방법은 irb 에서 코드 입력 시 제공하는 자동완성 기능에서 탭 키를 누르면 나오는 설명을 보는 것인데 alt + d 를 누르면 관련된 전체 내용을 볼 수 있다.

루비 문서는 훌륭한 참고 자료이지만 루비 문서만으로는 원하는 내용을 다 이해하기는 어려울 수도 있으므로 구글 검색도 함께 이용하도록 하자. 그리고 처음에는 해당 기능 전체 보다는 내가 지금 필요한 기능에 중점을 두고 예제 코드를 작성해 보면서 알아가는 것이 좋다.

 

위의  예외 처리 구문(begin ... rescue ... end) 예제를 다시 설명하면 timeout 메서드는 예외를 발생시킬 수 있기 때문에 begin 과 rescue 사이에 관련 코드를 작성하였고 rescue 와 end 사이에는 예외 발생 시 특별히 해야 할 일이 없기 때문에 어떠한 코드도 넣지 않았다. 그 결과 프로그램 실행 시 타임아웃으로 인해 실제로 예외가 발생했지만 irb 에서 예외 처리 구문을 사용하지 않고 테스트했을 때와는 달리 예외 관련 내용이 하나도 출력되지 않았다.

그래도 실제로 예외가 발생했는지 보고 싶다면 아래 처럼 코드를 수정하고 다시 실행해 보면 된다.

require 'timeout'

begin
  Timeout.timeout(0.1) do
    i = 1
    while true
      puts i
      i += 1
    end
  end
rescue
  puts "예외가 발생했다!"
end

 

이 번 글에서는 게임 작성 보다는 다른 얘기를 더 많이 하게 된 것 같은데 다음 글에서는 본격적으로 '스피드 연산 게임'의 코드를 작성해 보자.

See you again~~