Program Arcade Games With Python And Pygame

Program Arcade Games
With Python And Pygame

Chapter 4: 임의의 숫자와 반복루프를 활용한 숫자 알아맞추기 게임

그래픽을 들어가기 전 마지막 단계로 코드에 반복루프 작성하는 법을 알아 보자 대다수의 게임들은 같은 코드를 계속 해서 반복하는 “루프(loop)”로 이루어져 있다. 예를 들면 아래 루프처럼 플레이어가 매번 예상하는 값에 대해 너무 크거나 작다라는 결과를 반복적으로 알려 주는 것과 같다:

비디오: for 루프
안녕! 내가 1과 100사이의 임의의 숫자를 생각하고 있으니 맞춰봐.
--- 첫번째 시도
내가 생각한 숫자가 무엇일까: 50
너무 커.
--- 두번째 시도
내가 생각한 숫자가 무엇일까: 25
너무 커.
--- 세번째 시도
내가 생각한 숫자가 무엇일까: 17
너무 커.
--- 네번째 시도
내가 생각한 숫자가 무엇일까: 9
너무 작아.
--- 다섯번째 시도
내가 생각한 숫자가 무엇일까: 14
너무 커.
--- 여섯번째 시도
내가 생각한 숫자가 무엇일까: 12
너무 커.
--- 일곱번째 시도
내가 생각한 숫자가 무엇일까: 10
너무 작아.
이런, 기회를 다 써버렸군. 정답은 11 이야.

잠깐, 그런데 이게 그래픽이나 비디오 게임과 무슨 관련이 있을까? 많은 관련이 있다. 게임 화면에 표시되는 각 각의 프레임(frame)들은 루프를 한 번 돈 것이다. 여러분은 아마도 게임 사양에서 보여 주는 초 당 프레임수(frames-per-second, FPS)를 들어 봤을 것이다. FPS는 컴퓨터가 매 초당 화면을 업데이트하는 횟수를 표시한다. FPS가 높을 수록 게임의 움직임은 더 부드러워 진다.(비록 FPS가 60을 넘어가면 화면이 업데이트 할 수 있는 것보다 빠른 속도이긴 하나 그렇다고 그 이하로 한정할 만한 근거는 없다.) 그림 4.1 는 Eve online이라는 게임인데 그래프는 컴퓨터가 표시할 수 있는 FPS를 보여 주고 있다. fig.fps

Figure 4.1: 비디오 게임에서의 FPS

이러한 게임들에서의 루프는 그림 4.2의 순서도와 같이 동작한다. 현대 게임의 복잡성을 고려하더라도, 이러한 루프 내부의 동작은 우리가 1장에서 했던 계산기 프로그램과 유사하다. 사용자의 입력을 받고, 계산을 수행하고, 결과를 출력한다. 비디오게임에서는 이 과정을 초당 60번 반복하는 것이다. fig.game_loop

Figure 4.2: Game loop

하나의 루프안에 또 다른 루프가 있을 수 있다. “루프안의 루프”인 것이다. 그림 4.2의 “Draw Everything”박스를 보자. 이 코드는 루프를 돌면서 게임에서 필요한 모든 물체들을 그린다. 이러한 루프는 게임에서 각 각의 프레임을 그리는 더 큰 루프안에 들어 있는 형태로 그림4.3와 유사한 모습일 것이다.. fig.draw_everything

Figure 4.3: Draw everything loop

파이썬에는 두 가지 주요한 형태의 루프가 있는데, for 루프와 while 루프 이다. 일정 횟수 만큼 반복하고 싶다면 for 루프를 사용한다. 어떤 일이 발생할 때 까지 루프를 돌리고 싶다면 while 루프를 사용한다.(사용자가 중단 버튼을 눌렀다든지 하는..)

예를 들면 for 루프는 컴퓨터가 학생 수를 알고 있을 때 모든 학생들의 점수를 출력하는데 사용 할 수 있다. 반면 while 루프는 컴퓨터가 언제까지 기다려야 할지 모르는, 사용자의 마우스 버튼 입력을 체크하는데 사용 할 수 있다.

4.1 For 루프

아래 for 루프 예제는 print 문을 다섯 번 실행한다. 괄호안의 5를 원하는 숫자로 바꾸기만 하면 100번이고 1000번이고 쉽게 루프를 돌릴 수 있다. if문과 유사하게 for 루프가 작성될 수 있다는 것에 주목하다. 둘 다 끝에 콜론(:)을 찍고, 영향을 받는 구문을 표시하기 위해 들여쓰기를 사용한다.
for i in range(5):
    print("I will not chew gum in class.")
Output:
I will not chew gum in class.
I will not chew gum in class.
I will not chew gum in class.
I will not chew gum in class.
I will not chew gum in class.

첫번째 줄의 i 는 루프를 몇 번 돌았는지 기록하는 변수이다. 새롭게 선언된 변수이고 다양한 이름으로 붙여질 수 있다. 프로그래머들은 종종 i 를 변수명으로 사용하는데, i는 increment를 의미한다. 이 변수는 언제 루프가 끝날지를 확인하는데 사용되는 것이다.

range함수는 루프안의 코드가 몇 번 수행될지를 제어한다. 이 경우는 다섯 번이다.

다음 예제는 “Please,”를 다섯번 출력하고 “Can I go to the mall?”은 오직 한번만 출력한다. “Can I go to the mall?”은 들여쓰기되어 있지 않으므로 for 루프의 영향을 받지 않으므로 for 루프가 끝날때 까지는 출력되지 않는다.
for i in range(5):
    print("Please,")
print("Can I go to the mall?")
Output:
Please,
Please,
Please,
Please,
Please,
Can I go to the mall?

이번 예제는 앞의 예제를 가져다가 세번째 줄만 들여쓰기를 하였다.이렇게 하면 프로그램은 “Please," 와 "Can I go to the mall?”을 다섯번 출력한다. 구문이 들여쓰기가 되었으므로 “Can I go to the mall?”은 이제 for 루프의 일부가 되어 “Please,"처럼 다섯번 반복하여 출력되게 된다.

for i in range(5):
    print("Please,")
    print("Can I go to the mall?")
Output:
Please,
Can I go to the mall?
Please,
Can I go to the mall?
Please,
Can I go to the mall?
Please,
Can I go to the mall?
Please,
Can I go to the mall?

아래 코드는 숫자 0 에서 9까지를 출력할 것이다. 루프가 0에서 시작해서 10은 포함하지 않는다는 것에 주의하라. range(10)이 10을 포함할 것으로 생각할 수 있는데, 그게 아니고 바로 앞 숫자에서 멈춘다.
for i in range(10):
    print(i)
Output:
0
1
2
3
4
5
6
7
8
9

프로그램에서 반드시 변수명을 i로 해야하는 것은 아니며, 다른 문자를 사용할 수 도 있다. 예를 들자면 프로그래머는 lineNumber를 사용 할 수도 있다.

0 부터 9가 아니라 0 부터 10까지 가 길 원한다면, 몇가지 방법이 있다. 첫 번째 방법은 range 함수에 두 개의 입력을 사용하는 것이다. 첫 번째 값은 시작값, 두번째 값은 마지막값의 바로 다음 숫자를 입력하는 것이다.

for루프가 첫번째 숫자는 포함하고 두번째 숫자는 포함하지 않는다는 것에 익숙해지려면 상당한 연습이 필요하다. 아래 예제에서는 (1,11)으로 범위를 지정했으므로, 1 에서 10까지가 출력될 것이다. 처음 숫자 1은 포함되나, 두번째 숫자 11은 포함되지 않는다.
for i in range(1, 11):
    print(i)
Output:
1
2
3
4
5
6
7
8
9
10

1 에서 10까지 출력하는 또다른 방법은 range(10) 을 그대로 사용하는 것인데 이 경우 변수 i 는 0 에서 9까지의 값을 갖는다. 그래서 변수 값을 그냥 출력하기 전에 그 값에 1을 더하는 것다. 화면에 1 에서 10까지 출력될 것이다. 어떤 방법을 써도 좋다.

# Print the numbers 1 to 10.
for i in range(10):
    print(i + 1)

4.1.1 둘 이상 증가하는 숫자세기

2씩 또는 그 이상의 값으로 증가하여 숫자를 셀 필요가 있을 수 있는데, 이것도 쉽다. 이전처럼 두가지 방법이 있다. 가장 쉬운 방법은 range 함수에 2 씩 증가하라고 세번째 인수를 넣어주는 것이다. 두번째 방법은 1씩 증가하되 그 값에 2를 곱해주는 것이다. 아래 예제가 두가지 방법을 보여 주고 있다.
# Two ways to print the even numbers 2 to 10
for i in range(2,12,2):
    print(i)

for i in range(5):
    print((i + 1) * 2)
Output:
2
4
6
8
10
2
4
6
8
10

range 함수에 음수의 값을 줘서 0을 향해 역으로 세어나가는 것도 가능하다. 아래 예제는 10에서 시작해서 -1씩 거꾸로 세어나가며 0을 포함하지는 않는다. 이러한 루프를 만드는데 가장 어려운 부분은 시작과 마지막 숫자를 잘못 입력하지 않는 것이다. 프로그램은 큰 값에서 시작하므로 첫번째 인수로 넣어야 한다. for 루프는 range의 첫번째 인수로 작은 값을 넣어 증가하는 방향으로 세어 나가는 것이 일반적이다.
for i in range(10, 0, -1):
    print(i)
Output:
10
9
8
7
6
5
4
3
2
1

프로그램에서 반복해서 출력해야 할 숫자가 일정 패턴을 갖고 있지 않다면 "리스트"로 부터 숫자들을 끄집어 내는 것도 가능하다. (리스트에 대한 자세한 내용은 7장에서 다룬다. 어떤 식으로 쓸 수 있는지 간략히 살펴보자.)

for i in [2,6,4,2,4,6,7,4]:
    print(i)

This prints:

2
6
4
2
4
6
7
4

4.1.2 내포된 루프(Nesting Loops)

아래 코드가 어떻게 출력될지 예상 해 보라. 코드를 입력해 보고 예상이 맞는지 확인 해보자.

# What does this print? Why?
for i in range(3):
    print("a")
for j in range(3):
	print("b")
Video: Nesting for loops

다음 코드는 위와 거의 유사 하지만 두번째 for 루프가 tap 하나 만큼 들여쓰기가 되어 있어 첫번째 for루프에 내포된(nested) 루프가 된다. 이로 인해 코드 수행 결과는 완전히 달라진다. 입력 해서 실행시켜 보자.

# What does this print? Why?
for i in range(3):
    print("a")
    for j in range(3):
        print("b")

print("Done")

코드가 어떻게 작동하는지 설명하는 것보다는 직접 컴퓨터에서 실행 시켜보라.

Video: for loop animation

4.1.3 총합 계산하기

루프를 가지고 하는 흔한 작업 중 하나는 총합(Total)을 계산하여 저장하는 것이다. 이 “총합 계산” 코드 패턴은 이 책에서 아주 많이 사용된다. 점수의 총합을 계산해서 저장한거나 어떤이의 계좌 거래 총량을 계산한다든지 값들의 평균을 찾기 위해 총합을 계산한다든지 등이다. 우리는 나중에 여러번 이 부분을 참고할 것이므로 여러분은 여기의 코드 리스트들을 북마크해 놓는 것이 좋다. 아래 코드에서 사용자는 다섯 번 숫자를 입력할 것이고 코드는 그 숫자들의 총합을 계산할 것이다.

total = 0
for i in range(5):
    new_number = int(input("Enter a number: " ))
    total += new_number
print("The total is: ", total)
Video: Using a loop to keep a running total

첫번째 라인에서 변수 total을 선언하고, 초기값을 0으로 설정하였다. 변수를 선언하고 0으로 초기화하는 것을 잊어버리기 쉽다. 이 과정을 빼먹으면 컴퓨터가 네번째 라인을 실행할 때 짜증 섞인 에러를 낼 수 있다. total이라는 값이 아직 주어지지 않았기 때문에 컴퓨터는 new_number 에다가 total을 어떻게 더해야 할지 모른다.

흔한 실수 중 하나가 총합(total)을 위해 new_number 대신에 i 를 사용 하는 것이다. 우리는 현재 루프 수행의 총 횟수를 계산하는 것이 아니라 사용자 입력값의 총합을 계산하려 한다는 것을 기억하라.

만약 현재 루프가 돌고 있는 횟수에 대해 얘기하고자 한다면, 몇가지 수학적 문제들을 풀기위해 루프 카운트(loop count)값을 사용 할 수 있다. 예를 들면:

$s=\sum\limits_{n=1}^{100}n$

이런 형태의 표현에 익숙하지 않다면, 아래와 같은 직관적인 형태로 표시 할 수 있다 :

$s=1+2+3+4+5 \ldots 98+99+100$

아래 코드는 1 부터 100까지의 모든 숫자를 더한다. 루프안에서 총합의 값을 저장하고 있는 일반적인 패턴을 보여 준다. 이 코드에서는 또한 총합값을 저장하기 위해 sum이라는 다른 변수를 사용하고 있다.

# What is the value of sum?
sum = 0
for i in range(1, 101):
    sum = sum + i
print(sum)

여기 다른 형태의 코드를 보자. 사용자로 부터 다섯 개의 숫자를 입력 받는 동안 0을 몇 번 입력했는지를 센다:

total = 0
for i in range(5):
    new_number = int(input( "Enter a number: "))
    if new_number == 0:
        total += 1
print("You entered a total of", total, "zeros")

내포된(nested) for 루프와 총합계산을 이해한 프로그래머라면 아래 코드의 결과값을 예상할 수 있어야 한다.

# What is the value of a?
a = 0
for i in range(10):
    a = a + 1
print(a)

# What is the value of a?
a = 0
for i in range(10):
    a = a + 1
for j in range(10):
    a = a + 1
print(a)

# What is the value of a?
a = 0
for i in range(10):
    a = a + 1
    for j in range(10):
        a = a + 1
print(a)

이번 섹션을 너무 급하게 넘어가지 않았으면 좋겠다. 몇 가지 연습을 해보고 위 코드의 결과들을 예상해 보자. 그런 다음 파이썬 프로그램에 입력해 넣고 실행시켜서 결과가 맞았는지 확인 해 보라. 틀렸다면 왜 틀렸는지를 찾아 보자.

4.2 for 루프 예제들

이 예제 코드들은 일반적인 for 루프를 다루며 어떻게 동작하는지를 보여 줄 것이다.

# Sample Python/Pygame Programs
# Simpson College Computer Science
# http://programarcadegames.com/
# http://simpson.edu/computer-science/

# Print 'Hi' 10 times
for i in range(10):
    print("Hi")

# Print 'Hello' 5 times and 'There' once
for i in range(5):
    print("Hello")
print("There")

# Print 'Hello' 'There' 5 times
for i in range(5):
    print("Hello")
    print("There")

# Print the numbers 0 to 9
for i in range(10):
    print(i)

# Two ways to print the numbers 1 to 10
for i in range(1, 11):
    print(i)

for i in range(10):
    print(i + 1)

# Two ways to print the even numbers 2 to 10
for i in range(2, 12, 2):
    print(i)

for i in range(5):
    print((i + 1) * 2)

# Count down from 10 down to 1 (not zero)
for i in range(10, 0, -1):
    print(i)

# Print numbers out of a list
for i in [2, 6, 4, 2, 4, 6, 7, 4]:
    print(i)

# What does this print? Why?
for i in range(3):
    print("a")
    for j in range(3):
        print("b")

# What is the value of a?
a = 0
for i in range(10):
    a = a + 1
print(a)

# What is the value of a?
a = 0
for i in range(10):
    a = a + 1
for j in range(10):
    a = a + 1
print(a)

# What is the value of a?
a = 0
for i in range(10):
    a = a + 1
    for j in range(10):
        a = a + 1
print(a)

# What is the value of sum?
sum = 0
for i in range(1, 101):
    sum = sum + i

4.3 While 루프

Video: The while loop

for 루프는 프로그램이 해당 코드 블록을 몇 번 반복 실행 할지 아는 경우에 사용된다. 반면에 while 루프는 특정 조건이 발생할 때 까지 루프를 돌릴 때 주로 사용한다.

이상하게 들릴 수도 있지만 while 루프는 모든 for 루프를 대체하여 사용 할 수도 있다. 증가하는 변수 값이 특정값에 도달할 때 까지 루프를 돌리는데 사용될 수 있기 때문이다. while 루프로 모든 루프를 처리 할 수 있는데 왜 for루프를 사용하는가? for 루프가 상대적으로 사용하기 쉽기 때문이다. for 루프는 아래 처럼 사용 할 수 있다 :

for i in range(10):
    print(i)

while 루프로도 가능한데 아래처럼 코딩 할 수 있다 :

i = 0
while i < 10:
    print(i)
    i = i + 1

첫번째 라인에서 while 루프는 루프가 수행된 횟수를 세기 위해 “보조(sentinel)” 변수를 선언했다. 이러한 작업은 for 루프에서는 자동으로 되는 것이므로 코드 한 줄이 줄게 된다. 두번째 줄에 실제 while 루프가 나온다. while 루프 형태는 if 문과 상당히 비슷하다. 만약 조건이 유지되면, 루프 안의 코드는 반복된다. 네번째 라인은 증가된 값을 더한다. for 루프에서는 이 부분도 자동으로 되는 부분이므로 또 코드 한 줄이 줄게 된다. 코드의 사례에서 보았듯이, for 루프가 while 루프보다 좀 더 간단하고 읽기 쉽다. 그렇지 않다면 모든 프로그램이 while 루프로만 쓰였을 것이다.

for 루프와 while 루프를 혼동하는 것은 흔한 실수이다. 아래 코드는 프로그래머가 for 루프를 쓸 지 while 루프를 쓸 지 결정을 못한 사례를 보여 준다.

while range(10):
    print(i)
Don't use range with a while loop!

range 함수는 오직 for 루프에서만 사용하라. while 루프에서는 사용하지 말라!

4.3.1 증가 연산자 사용하기

증가연산자(Increment operators)는 while 루프에서 자주 사용된다. 코드를 간단하게 작성하는 것이 가능하다 :

i = i + 1

다음 코드를 보자:

i += 1

while 루프에서 다음과 같이 사용 할 수 있다 :

i = 0
while i < 10:
    print(i)
    i += 1

뺄셈과 곱셈에서도 비슷한 형태로 사용할 수 있다. 예를 보자 :

i *= 2

이 것은 다음과 같다 :

i = i * 2

다음 코드가 무엇을 출력할지 알 수 있겠는가 :

i = 1
while i <= 2 ** 32:
    print(i)
    i *= 2

4.3.2 사용자가 그만 할때 까지 루프 돌기

가장 흔한 동작 중 하나가 사용자가 중단을 요청할 때 까지 루프를 도는 것이다:

quit = "n"
while quit == "n":
    quit = input("Do you want to quit? ")

중단을 위한 루프를 구현하는 것에는 몇가지 방법이 있다. 이진 변수(Boolean variable)를 사용해서 이벤트가 발생 했을 때 중단하는 것이다. 여기 예제를 보자:

done = False
while not done:
    quit = input("Do you want to quit? ")
    if quit == "y":
        done = True

    attack = input("Does your elf attack the dragon? ")
    if attack == "y":
        print("Bad choice, you died.")
        done = True

이 코드는 완벽하지 않은데, 왜냐하면 만약 사용자가 중단(quit)을 요청해도, 코드는 여전히 드레곤을 공격 할 것인지를 묻기 때문이다. 어떻게 고쳐야 할까?

다음 예제는 value가 0에 충분히 가까운 값이 될 때 까지 코드가 반복 수행되는 while 루프이다:

value = 0
increment = 0.5
while value < 0.999:
    value += increment
    increment *= 0.5
    print(value)

4.3.3 while 루프의 일반적인 문제들

프로그래머가 10부터 역으로 세어나가길 원한다. 무엇이 잘못 되었으며 어떻게 고치면 될까?

i = 10
while i == 0:
    print(i)
    i -= 1

10까지 세려고 하는 다음 코드는 무엇이 잘못 되었는가? 실행되었을 때 어떤 문제가 발생하는가? 어떻게 고쳐야 할까?

i = 1
while i < 10:
    print(i)

4.4 while 루프 예제들

우리가 조금 전까지 얘기한 다양한 형태의 while 루프들을 보여 준다.

# Sample Python/Pygame Programs
# Simpson College Computer Science
# http://programarcadegames.com/
# http://simpson.edu/computer-science/

# A while loop can be used anywhere a for loop is used:
i = 0
while i < 10:
    print(i)
    i = i + 1

# This is the same as:
for i in range(10):
    print(i)

# It is possible to short hand the code:
# i = i + 1
# With the following:
# i += 1
# This can be done with subtraction, and multiplication as well.
i = 0
while i < 10:
    print(i)
    i += 1

# What would this print?
i = 1
while i <= 2**32:
    print(i)
    i *= 2

# A very common operation is to loop until the user performs
# a request to quit
quit = "n"
while quit == "n":
    quit = input("Do you want to quit? ")

# There may be several ways for a loop to quit. Using a boolean
# to trigger the event is a way of handling that.
done = False
while not done:
    quit = input("Do you want to quit? ")
    if quit == "y":
        done = True

    attack = input("Does your elf attach the dragon? ")
    if attack == "y":
        print("Bad choice, you died.")
        done = True

value = 0
increment = 0.5
while value < 0.999:
    value += increment
    increment *= 0.5
    print(value)

# -- Common problems with while loops --

# The programmer wants to count down from 10
# What is wrong and how to fix it?
i = 10
while i == 0:
    print(i)
    i -= 1

# What is wrong with this loop that tries
# to count to 10? What will happen when it is run?
i = 1
while i < 10:
    print(i)

4.5 임의의 수

임의의 수(Random numbers)는 게임이나 시뮬레이션에 관련된 컴퓨터 과학에 정말 많이 사용된다.

4.5.1 randrange 함수

Video: Random Numbers

기본적으로 파이썬은 임의의 수를 생성하는 법을 모른다. 임의의 숫자를 생성하기 위해서는 적절한 라이브러리를 적재할(import) 필요가 있다. 임의의 숫자 생성을 위해 코드 제일 위에 import 문을 써 주어야 한다 :

import random

pygame과 마찬가지로, import할 파일 이름과 같은 이름으로 파일명을 만들지 않도록 주의 해야 한다. random.py 라는 이름의 파일을 만들어 버리면 파이썬은 랜덤 넘버를 생성하는 시스템 라이브러리가 아닌 사용자가 만든 파일을 가져오게 될 것이다.

그 다음, 임의의 숫자(랜덤 넘버)는 randrange 함수를 이용하여 생성할 수 있다. 예를 들어, 다음 코드는 0에서 49까지의 임의의 숫자를 생성해 낼 것이다. 하한치 값은 0으로 기본 설정 되어 있다.

my_number = random.randrange(50)

다음 예제는 100에서 200까지의 임의의 숫자를 발생시킨다. range 함수처럼 두번째 인자(parameter)는 상한치를 설정하고 그 값 자체는 포함되지 않는다. 따라서 여러분이 200을 포함하는 랜덤 넘버를 생성하고자 한다면 201로 입력해 주어야 한다.

my_number = random.randrange(100, 201)

만약 임의의 숫자가 아니라 임의의 물건(item)을 원한다면? 먼저 리스트(list)가 필요하다. 7장까지는 리스트에 대해 자세히 다루지는 않지만, 어떻게 리스트에서 임의의 아이템을 선택하는지를 간단히 살펴 보자:

my_list = ["rock", "paper", "scissors"]
random_index = random.randrange(3)
print(my_list[random_index])

4.5.2 random 함수

이전까지의 코드들은 정수(integer)만을 생성해 내었다. 실수(floating point number)가 필요한 경우에는 random 함수를 사용 할 수 있다.

아래 코드는 0.4355991106620656와 같은 0과 1사의 임의의 실수를 생성해 낸다.

my_number = random.random()

간단한 수학을 사용해서 생성되는 숫자들의 조절이 가능하다. 예를 들면 아래 코드는 10과 15사이의 임의의 실수를 생성해 낸다:

my_number = random.random() * 5 + 10

4.6 Review

4.6.1 Multiple Choice Quiz

Click here for a multiple-choice quiz.

4.6.2 Short Answer Worksheet

Click here for the chapter worksheet.

4.6.3 Lab

Click here for the chapter lab.


You are not logged in. Log in here and track your progress.