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

스피드 연산 게임 만들기6

by 경자꿈사 2024. 7. 30.

오늘은 '스피드 연산 게임 만들기' 시리즈의 마지막 글이 될 것 같다. 우선 정수로 나누어 떨어지는 나눗셈 문제를 추가로 넣은 후 게임 플레이를 irb가 아닌 cmd 창에서 바로 실행할 수 있게 만들려고 한다. 또한 실행 화면에서 게임 시간과 문제를 직접 선택할 수 있는 기능도 넣어보겠다.

아래와 같이 먼저 나눗셈 문제를 생성하는 클래스를 만들고 테스트해 보자.

class Gugudan
  def make
    n1 = rand(2..9)
    n2 = rand(1..9)
    ["#{n1} x #{n2} = ", n1 * n2]  
  end
end

class Add
  def make
    n1 = rand(1..9)
    n2 = rand(1..9)
    ["#{n1} + #{n2} = ", n1 + n2]    
  end
end
  
class Sub
  def make
    n1 = rand(1..9)
    n2 = rand(1..9)
    ["#{n1} - #{n2} = ", n1 - n2]    
  end
end

class Div
  def make
    n1 = rand(1..9)
    n2 = rand(1..9)
    ["#{n1 * n2} ÷ #{n2} = ", n1]    
  end
end
>> require './quiz'
=> true
>> quiz = Div.new
=> #<Div:0x00000235668ffe88>
>> quiz.make
=> ["1 ÷ 1 = ", 1]
>> quiz.make
=> ["24 ÷ 8 = ", 3]
>> quiz.make
=> ["25 ÷ 5 = ", 5]

기존 quiz.rb 파일에 Div 라는 클래스를 추가로 생성하였다. 그리고 나눗셈 문제는 구구단 범위 내에서 문제가 만들어지도록 했다. 즉 구구단의 결괏값(n1 * n2)이 나눗셈의 피제수가 되고 구구단의 피연산자(n1)가 나눗셈의 결괏값이 된다. 테스트 결과 나눗셈 문제가 잘 만들어지는 게 보인다.

이제 cmd 창에서 스피드 게임 실행을 위해 프로그램 메인 역할을 할 소스 파일을 하나 만들고 관련된 모든 소스 파일과 문제 파일들을 모두 하나의 폴더에 모아 넣자.

아래 그림을 보면 speed_quiz 라는 폴더가 하나 보이고 그 폴더 안에 workbook 이라는 폴더가 또 하나 있다. 소스 파일들은 speed_quiz 폴더 바로 아래 두었고 문제 파일들은 모두 workbook 이라는 폴더 아래 두었다.

객관식 문제도 선택할 수 있도록 '영어문제.txt' 파일을 하나 더 만들어 보았는데 아래 보이는 것처럼 루비 문제 파일과 동일한 규칙으로 작성하였다.

영어문제.txt
0.00MB

그리고 speed_quiz.rb 파일이 프로그램의 메인 역할을 할 소스 파일이다. 우선 간단하게 아래와 같이 작성하고 바로 실행해 보자. irb에서 테스트를 편하게 하기 위해 game.rb 소스에서 quiz.rb 소스를 로드했는데 이제 메인 역할을 하는 소스 파일이 생겼으니 game.rb 에서 require './quiz' 코드를 삭제하고 speed_quiz.rb 에 그 코드를 추가하자.

require './game'
require './quiz'
require './choice_quiz'

game = Game.new
game.quiz = ChoiceQuiz.new("./workbook/영어문제.txt")
game.play

speed_quiz 폴더와 같은 위치에서 cmd 창을 열고 명령 프롬프트에 "ruby speed_quiz.rb" 라고 입력을 하면 speed_quiz.rb 소스 파일에 입력된 코드 그대로 영어 문제를 푸는 스피드 게임이 시작된다.

이제 남은 일은 게임 시간과 문제 유형을 선택할 수 있는 기능을 넣는 일이다. 이 기능도 문제를 푸는 것과 비슷하게 사용자로부터 입력을 받아 처리하는 코드를 작성해야 하는 일이다.

우선 게임을 원하는 만큼 여러 번 할 수 있어야 한다고 보면 반복문이 필요하고 각 게임마다 게임 시간과 문제를 다르게 선택할 수 있게 하려면 게임을 시작하기 전에 사용자에게 입력을 받아야 하는데 선택 사항이 여러 개이므로 메뉴 형식으로 보여주면 좋을 것 같다.

우선 아래과 같이 게임을 계속 진행할지 종료할지 물어보도록 코드를 수정해 보자.

require './game'
require './choice_quiz'

game = Game.new
game.quiz = ChoiceQuiz.new("./workbook/영어문제.txt")

while true
  game.play
  print "게임을 다시 하시겠습니까? (y/n) "
  input = STDIN.gets.chomp
  break if input != "y"
end

코드를 보면 키보드 입력을 받아오는 부분에 chomp 메서드가 보일텐데 이 메서드는 키보드 입력 결과로 받는 문자열의 가장 끝에 붙어 있는 개행문자('\n') 하나를 없애는 역할을 한다. chomp 메서드를 빼고 실행을 하게 되면 'y' 를 입력해도 실제로는 "y\n" 과 "y" 을 비교하게 되어 같지 않게 되므로 게임을 다시 시작하지 못하고 종료되어 버린다.

이제는 게임 시간과 문제 유형을 선택할 수 있게 해보자.

최상위 메뉴에 대한 출력과 선택을 처리하는 코드를 아래와 같이 작성할 수 있다. menu 메서드의 코드를 보면 우선 3 개의 선택 가능한 메뉴를 사용자에게 보여주고 입력을 기다린다. 그리고 입력 받은 값과 메뉴 번호를 비교하여 필요한 처리를 하게 되는데 사용자가 '문제 유형 설정' 이나 '게임 시간 설정' 메뉴를 선택했을 경우에는 별도의 메서드를 호출하여 이후의 처리를 맡도록 되어 있다. 특이한 점은 menu 메서드의 가장 아래 줄을 보면 메서드 내에서 다시 자기 자신을 호출하고 있는데 이러한 것을 '재귀 호출(recursive call)'이라고 부른다.

사용자가 먼저 문제 유형을 설정한 다음 게임 시간까지 설정한 후에 게임을 시작할 수도 있고 아니면 문제 유형을 다시 변경하고 싶을 수도 있다. 즉 사용자가 '바로 게임 시작' 메뉴를 선택하기 전에는 몇 번이든 반복해서 문제 유형과 시간을 설정할 수 있어야 한다. 그래서 재귀 호출을 통해 이러한 과정을 반복할 수 있도록 하였다.

 

def menu(game)
  puts "1. 바로 게임 시작"
  puts "2. 문제 유형 선택"
  puts "3. 게임 시간 설정"
  print "선택 > "
  input = STDIN.gets.to_i
  if input == 1
    return
  elsif input == 2
    set_quiz(game)
  elsif input == 3
    set_time(game)
  else
    puts "잘못 선택하셨습니다."
  end
  menu(game)
end

다음으로 먼저 '문제 유형 설정' 기능 보다는 조금 더 간단한 '게임 시간 설정' 기능에 대한 코드를 살펴 보자.

def set_time(game)
  puts "[게임 시간 설정]"
  print "10 ~ 30 사이를 입력해 주세요 > "
  input = STDIN.gets.to_i
  if input >= 10 && input <= 30
    game.time = input
  else
    puts "잘못 입력하셨습니다."
    set_time(game)
  end
end

게임 시간은 너무 짧거나 길면 게임이 제대로 진행되기 어렵거나 흥미가 떨어질 수 있으므로 10~30초 사이로 제한을 두었다. 

이제 마지막으로 사용자가 문제 유형을 직접 선택할 수 있도록 해주는 기능을 볼 차례이다. 이 기능은 객관식 문제의 경우 다시 문제 파일을 선택해야 하는 서브 메뉴로 들어가기 때문에 게임 시간 설정보다는 조금 복잡하다. 아래 코드를 보자.

def set_quiz(game)
  puts "[문제 유형 선택]"
  puts "1. 구구단"
  puts "2. 덧셈"
  puts "3. 뺄셈"
  puts "4. 나눗셈"
  puts "5. 객관식"
  puts "6. 상위 메뉴로 가기"
  print "선택 > "
  input = STDIN.gets.to_i
  if input == 1
    game.quiz = Gugudan.new
  elsif input == 2
    game.quiz = Add.new  
  elsif input == 3
    game.quiz = Sub.new  
  elsif input == 4
    game.quiz = Div.new  
  elsif input == 5
    set_quiz(game) if !set_choice_quiz(game)
  elsif input == 6
    return
  else
    puts "잘못 선택하셨습니다."
    set_quiz(game)
  end
end

def set_choice_quiz(game)
  puts "[객관식 문제 선택]"
  puts "1. 루비문제"
  puts "2. 영어문제"
  puts "3. 상위 메뉴로 가기"
  print "선택 > "
  input = STDIN.gets.to_i
  if input == 1
    game.quiz = ChoiceQuiz.new("./workbook/루비문제.txt")
    return true
  elsif input == 2
    game.quiz = ChoiceQuiz.new("./workbook/영어문제.txt")
    return true
  elsif input == 3
    return false
  else
    puts "잘못 선택하셨습니다."
    set_choice_quiz(game)
  end
end

set_quiz 메서드를 보면 seq_choce_quiz 메서드의 호출 결과가 true 가 아니면 다시 seq_quiz 를 재귀 호출하도록 되어 있는데 이것은 사용자가 객관식 문제를 선택한 후에 문제 파일을 선택하지 않고 그냥 상위 메뉴로 돌아가는 경우에 대한 처리가 필요하기 때문이다. 그래서 seq_choice_quiz 메서드를 보면 사용자가 '루비문제' 나 '영어문제'를 선택할 경우에는 true 를 반환하고 '상위 메뉴로 가기' 를 선택할 경우에는 false 를 반환하도록 되어 있다.

지금까지의 사용자 메뉴 선택 및 처리 코드를 포함한 speed_quiz.rb 의 전체 코드는 아래와 같다.

require './game'
require './choice_quiz'

game = Game.new

def menu(game)
  puts "\n[게임 메뉴]"
  puts "1. 바로 게임 시작"
  puts "2. 문제 유형 선택"
  puts "3. 게임 시간 설정"
  print "선택 > "
  input = STDIN.gets.to_i
  if input == 1
    return
  elsif input == 2
    set_quiz(game)
  elsif input == 3
    set_time(game)
  else
    puts "잘못 선택하셨습니다."
  end
  menu(game)
end

def set_quiz(game)
  puts "\n[문제 유형 선택]"
  puts "1. 구구단"
  puts "2. 덧셈"
  puts "3. 뺄셈"
  puts "4. 나눗셈"
  puts "5. 객관식"
  puts "6. 상위 메뉴로 가기"
  print "선택 > "
  input = STDIN.gets.to_i
  if input == 1
    game.quiz = Gugudan.new
  elsif input == 2
    game.quiz = Add.new  
  elsif input == 3
    game.quiz = Sub.new  
  elsif input == 4
    game.quiz = Div.new  
  elsif input == 5
    set_quiz(game) if !set_choice_quiz(game)
  elsif input == 6
    return
  else
    puts "잘못 선택하셨습니다."
    set_quiz(game)
  end
end

def set_choice_quiz(game)
  puts "\n[객관식 문제 선택]"
  puts "1. 루비문제"
  puts "2. 영어문제"
  puts "3. 상위 메뉴로 가기"
  print "선택 > "
  input = STDIN.gets.to_i
  if input == 1
    game.quiz = ChoiceQuiz.new("./workbook/루비문제.txt")
    return true
  elsif input == 2
    game.quiz = ChoiceQuiz.new("./workbook/영어문제.txt")
    return true
  elsif input == 3
    return false
  else
    puts "잘못 선택하셨습니다."
    set_choice_quiz(game)
  end
end

def set_time(game)
  print "10 ~ 30 사이를 입력해 주세요 > "
  input = STDIN.gets.to_i
  if input >= 10 && input <= 30
    game.time = input
  else
    puts "잘못 입력하셨습니다."
    set_time(game)
  end
end

while true
  menu(game)
  game.play
  print "게임을 다시 하시겠습니까? (y/n) "
  input = STDIN.gets.chomp
  break if input != "y"
end

아래의 실행 화면을 보면 메뉴가 보여지고 선택한 것에 따라 게임이 진행되는 것을 볼 수 있다.

오늘은 사용자가 직접 문제 유형과 게임 시간을 선택할 수 있도록 하는 기능과 함께 cmd 창에서 바로 게임을 실행할 수 있도록 프로그램을 수정해 보았다.

그런데 메뉴를 보여주고 선택받고 처리하는 코드 때문에 메인 소스(speed_quiz.rb)가 조금 복잡해 보이는 게 아쉽다.

메뉴 처리와 관련된 상세 코드를 메인 소스에서 최대한 보이지 않도록 코드를 정리하면 좋을 것 같다. 또한 메뉴를 보여주고 처리하는 코드를 보면 어느 정도 패턴이 있기 때문에 조금 깊게 생각해 본다면 재활용할 수 있는 구조로 수정이 가능할 것도 같다. 다음 번 글은 이와 관련한 내용을 바탕으로 써볼까 한다.

See you again~~