장: 네 가지 추가 게임
프로토콜:CC BY-NC-SA 4.0
이 장에는 4개의 추가 게임에 대한 소스 코드가 포함되어 있습니다. 아쉽게도 이 장에는 소스 코드만 포함되어 있으며 코드에 대한 자세한 설명은 없습니다. 지금까지는 소스 코드와 주석을 보고 이 게임들을 플레이하고 코드가 어떻게 작동하는지 파악할 수 있습니다.
이러한 게임에는 다음이 포함됩니다:
플리피 - 플레이어가 컴퓨터 AI 플레이어의 큐브를 뒤집는 '오델로' 클론 게임입니다.
잉크 유출 - 플러드 채우기 알고리즘을 사용하는 '플러드 잇' 복제본입니다.
연속 4연승 - 컴퓨터 AI 플레이어와 대결하는 '커넥트 포' 클론 게임입니다.
보석 - 플레이어가 보석을 교환하여 같은 보석 세 개를 연속으로 획득하는 "보석" 클론입니다.
이 책의 소스 코드에 대해 궁금한 점이 있으면 언제든지 저자에게 이메일 ([email protected] 으로 문의하시기 바랍니다.
버그 수정을 연습하고 싶다면 이러한 프로그램의 버기 버전도 사용할 수 있습니다:
Flippy,"오델로”복제
리버스시라고도 하는 오델로는 한 면은 검은색, 다른 면은 흰색으로 된 8x8 보드입니다. 시작 보드는 그림 10-1에 나와 있습니다. 각 플레이어는 번갈아 가며 자신의 색깔의 새 사각형을 놓습니다. 새 사각형과 같은 색의 다른 사각형 사이에 있는 상대방의 사각형은 뒤집습니다. 게임의 목표는 가능한 한 많은 같은 색의 사각형을 갖는 것입니다. 예를 들어, 그림 10-2는 흰색이 5, 6에 새 흰색 사각형을 놓으면 어떻게 되는지 보여줍니다.
5, 5의 검은색 사각형은 새로운 흰색 사각형과 기존 흰색 사각형 5, 4 사이에 있습니다. 이 검은색 사각형이 뒤집혀서 새로운 흰색 사각형이 되면 보드는 그림 10-3과 같이 됩니다. 이 검은색 사각형이 뒤집혀 새로운 흰색 사각형이 되어 그림 10-3과 같은 보드가 됩니다. 그런 다음 검은색은 비슷한 방법으로 4, 6에 검은색 사각형을 배치하고 5, 4에 흰색 사각형을 뒤집습니다. 이렇게 하면 그림 10-4와 같은 보드가 만들어집니다.
사각형은 플레이어의 새 사각형과 기존 사각형 사이에 있는 한 모든 방향으로 뒤집습니다. 그림 10-5에서 백인은 3, 6에 정사각형을 배치하고 검은색 정사각형을 양방향으로 뒤집습니다. 결과는 그림 10-6에 나와 있습니다.
보시다시피, 각 플레이어는 한두 번의 이동으로 보드의 대부분의 사각형을 빠르게 차지할 수 있습니다. 플레이어는 항상 한 칸 이상을 점유하는 이동을 해야 합니다. 플레이어가 움직일 수 없거나 보드가 완전히 가득 차면 게임이 종료됩니다. 자신의 색깔의 사각형을 가장 많이 차지한 플레이어가 승리합니다.
리버시에 대한 자세한 내용은 invpy.com/buggy/gemgem 확인할 수 있습니다.
파이게임 대신 인쇄() 및 입력()을 사용하는 이 게임의 텍스트 버전은 15장, '파이썬으로 나만의 컴퓨터 게임 발명하기'에 나와 있습니다. 해당 챕터에서 컴퓨터 AI 알고리즘이 어떻게 구성되는지 배울 수 있습니다. en.wikipedia.org/wiki/Reversi
컴퓨터가 가능한 모든 움직임을 쉽게 시뮬레이션하고 가장 많은 타일을 뒤집는 움직임을 선택하기 때문에 이 게임의 컴퓨터 AI는 훌륭합니다. 제가 플레이할 때마다 항상 저를 이깁니다.
Flippy 소스 코드
이 소스 코드는 inventwithpython.com/chapter15.html 다운로드할 수 있습니다.
Flippy에서 사용하는 이미지 파일은 invpy.com/flippyimages.zip 다운로드할 수 있습니다.
# Flippy (an Othello or Reversi clone)
# By Al Sweigart [[email protected]](/cdn-cgi/l/email-protection)
# http://.com/pygame
# Released under a "Simplified BSD" license
# Based on the "reversi.py" code that originally appeared in "Invent
# Your Own Computer Games with Python", chapter 15:
# http://.com/.html
import random, sys, pygame, time, copy
from pygame.locals import *
FPS = 10 # frames per second to update the screen
WINDOWWIDTH = 640 # width of the program's window, in pixels
WINDOWHEIGHT = 480 # height in pixels
SPACESIZE = 50 # width & height of each space on the board, in pixels
BOARDWIDTH = 8 # how many columns of spaces on the game board
BOARDHEIGHT = 8 # how many rows of spaces on the game board
WHITE_TILE = 'WHITE_TILE' # an arbitrary but unique value
BLACK_TILE = 'BLACK_TILE' # an arbitrary but unique value
EMPTY_SPACE = 'EMPTY_SPACE' # an arbitrary but unique value
HINT_TILE = 'HINT_TILE' # an arbitrary but unique value
ANIMATIONSPEED = 25 # integer from 1 to 100, higher is faster animation
# Amount of space on the left & right side (XMARGIN) or above and below
# (YMARGIN) the game board, in pixels.
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * SPACESIZE)) / 2)
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * SPACESIZE)) / 2)
# R G B
WHITE = (255, 255, 255)
BLACK = ( 0, 0, 0)
GREEN = ( 0, 155, 0)
BRIGHTBLUE = ( 0, 50, 255)
BROWN = (174, 94, 0)
TEXTBGCOLOR1 = BRIGHTBLUE
TEXTBGCOLOR2 = GREEN
GRIDLINECOLOR = BLACK
TEXTCOLOR = WHITE
HINTCOLOR = BROWN
def main():
global MAINCLOCK, DISPLAYSURF, FONT, BIGFONT, BGIMAGE
pygame.init()
MAINCLOCK = pygame.time.Clock()
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
pygame.display.set_caption('Flippy')
FONT = pygame.font.Font('freesansbold.ttf', 16)
BIGFONT = pygame.font.Font('freesansbold.ttf', 32)
# Set up the background image.
boardImage = pygame.image.load('flippyboard.png')
# Use smoothscale() to stretch the board image to fit the entire board:
boardImage = pygame.transform.smoothscale(boardImage, (BOARDWIDTH * SPACESIZE, BOARDHEIGHT * SPACESIZE))
boardImageRect = boardImage.get_rect()
boardImageRect.topleft = (XMARGIN, YMARGIN)
BGIMAGE = pygame.image.load('flippybackground.png')
# Use smoothscale() to stretch the background image to fit the entire window:
BGIMAGE = pygame.transform.smoothscale(BGIMAGE, (WINDOWWIDTH, WINDOWHEIGHT))
BGIMAGE.blit(boardImage, boardImageRect)
# Run the main game.
while True:
if runGame() == False:
break
def runGame():
# Plays a single game of reversi each time this function is called.
# Reset the board and game.
mainBoard = getNewBoard()
resetBoard(mainBoard)
showHints = False
turn = random.choice(['computer', 'player'])
# Draw the starting board and ask the player what color they want.
drawBoard(mainBoard)
playerTile, computerTile = enterPlayerTile()
# Make the Surface and Rect objects for the "New Game" and "Hints" buttons
newGameSurf = FONT.render('New Game', True, TEXTCOLOR, TEXTBGCOLOR2)
newGameRect = newGameSurf.get_rect()
newGameRect.topright = (WINDOWWIDTH - 8, 10)
hintsSurf = FONT.render('Hints', True, TEXTCOLOR, TEXTBGCOLOR2)
hintsRect = hintsSurf.get_rect()
hintsRect.topright = (WINDOWWIDTH - 8, 40)
while True: # main game loop
# Keep looping for player and computer's turns.
if turn == 'player':
# Player's turn:
if getValidMoves(mainBoard, playerTile) == []:
# If it's the player's turn but they
# can't move, then end the game.
break
movexy = None
while movexy == None:
# Keep looping until the player clicks on a valid space.
# Determine which board data structure to use for display.
if showHints:
boardToDraw = getBoardWithValidMoves(mainBoard, playerTile)
else:
boardToDraw = mainBoard
checkForQuit()
for event in pygame.event.get(): # event handling loop
if event.type == MOUSEBUTTONUP:
# Handle mouse click events
mousex, mousey = event.pos
if newGameRect.collidepoint( (mousex, mousey) ):
# Start a new game
return True
elif hintsRect.collidepoint( (mousex, mousey) ):
# Toggle hints mode
showHints = not showHints
# movexy is set to a two-item tuple XY coordinate, or None value
movexy = getSpaceClicked(mousex, mousey)
if movexy != None and not isValidMove(mainBoard, playerTile, movexy[0], movexy[1]):
movexy = None
# Draw the game board.
drawBoard(boardToDraw)
drawInfo(boardToDraw, playerTile, computerTile, turn)
# Draw the "New Game" and "Hints" buttons.
DISPLAYSURF.blit(newGameSurf, newGameRect)
DISPLAYSURF.blit(hintsSurf, hintsRect)
MAINCLOCK.tick(FPS)
pygame.display.update()
# Make the move and end the turn.
makeMove(mainBoard, playerTile, movexy[0], movexy[1], True)
if getValidMoves(mainBoard, computerTile) != []:
# Only set for the computer's turn if it can make a move.
turn = 'computer'
else:
# Computer's turn:
if getValidMoves(mainBoard, computerTile) == []:
# If it was set to be the computer's turn but
# they can't move, then end the game.
break
# Draw the board.
drawBoard(mainBoard)
drawInfo(mainBoard, playerTile, computerTile, turn)
# Draw the "New Game" and "Hints" buttons.
DISPLAYSURF.blit(newGameSurf, newGameRect)
DISPLAYSURF.blit(hintsSurf, hintsRect)
# Make it look like the computer is thinking by pausing a bit.
pauseUntil = time.time() + random.randint(5, 15) * 0.1
while time.time() < pauseUntil:
pygame.display.update()
# Make the move and end the turn.
x, y = getComputerMove(mainBoard, computerTile)
makeMove(mainBoard, computerTile, x, y, True)
if getValidMoves(mainBoard, playerTile) != []:
# Only set for the player's turn if they can make a move.
turn = 'player'
# Display the final score.
drawBoard(mainBoard)
scores = getScoreOfBoard(mainBoard)
# Determine the text of the message to display.
if scores[playerTile] > scores[computerTile]:
text = 'You beat the computer by %s points! Congratulations!' % \
(scores[playerTile] - scores[computerTile])
elif scores[playerTile] < scores[computerTile]:
text = 'You lost. The computer beat you by %s points.' % \
(scores[computerTile] - scores[playerTile])
else:
text = 'The game was a tie!'
textSurf = FONT.render(text, True, TEXTCOLOR, TEXTBGCOLOR1)
textRect = textSurf.get_rect()
textRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))
DISPLAYSURF.blit(textSurf, textRect)
# Display the "Play again?" text with Yes and No buttons.
text2Surf = BIGFONT.render('Play again?', True, TEXTCOLOR, TEXTBGCOLOR1)
text2Rect = text2Surf.get_rect()
text2Rect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) + 50)
# Make "Yes" button.
yesSurf = BIGFONT.render('Yes', True, TEXTCOLOR, TEXTBGCOLOR1)
yesRect = yesSurf.get_rect()
yesRect.center = (int(WINDOWWIDTH / 2) - 60, int(WINDOWHEIGHT / 2) + 90)
# Make "No" button.
noSurf = BIGFONT.render('No', True, TEXTCOLOR, TEXTBGCOLOR1)
noRect = noSurf.get_rect()
noRect.center = (int(WINDOWWIDTH / 2) + 60, int(WINDOWHEIGHT / 2) + 90)
while True:
# Process events until the user clicks on Yes or No.
checkForQuit()
for event in pygame.event.get(): # event handling loop
if event.type == MOUSEBUTTONUP:
mousex, mousey = event.pos
if yesRect.collidepoint( (mousex, mousey) ):
return True
elif noRect.collidepoint( (mousex, mousey) ):
return False
DISPLAYSURF.blit(textSurf, textRect)
DISPLAYSURF.blit(text2Surf, text2Rect)
DISPLAYSURF.blit(yesSurf, yesRect)
DISPLAYSURF.blit(noSurf, noRect)
pygame.display.update()
MAINCLOCK.tick(FPS)
def translateBoardToPixelCoord(x, y):
return XMARGIN + x * SPACESIZE + int(SPACESIZE / 2), YMARGIN + y * SPACESIZE + int(SPACESIZE / 2)
def animateTileChange(tilesToFlip, tileColor, additionalTile):
# Draw the additional tile that was just laid down. (Otherwise we'd
# have to completely redraw the board & the board info.)
if tileColor == WHITE_TILE:
additionalTileColor = WHITE
else:
additionalTileColor = BLACK
additionalTileX, additionalTileY = translateBoardToPixelCoord(additionalTile[0], additionalTile[1])
pygame.draw.circle(DISPLAYSURF, additionalTileColor, (additionalTileX, additionalTileY), int(SPACESIZE / 2) - 4)
pygame.display.update()
for rgbValues in range(0, 255, int(ANIMATIONSPEED * 2.55)):
if rgbValues > 255:
rgbValues = 255
elif rgbValues < 0:
rgbValues = 0
if tileColor == WHITE_TILE:
color = tuple([rgbValues] * 3) # rgbValues goes from 0 to 255
elif tileColor == BLACK_TILE:
color = tuple([255 - rgbValues] * 3) # rgbValues goes from 255 to 0
for x, y in tilesToFlip:
centerx, centery = translateBoardToPixelCoord(x, y)
pygame.draw.circle(DISPLAYSURF, color, (centerx, centery), int(SPACESIZE / 2) - 4)
pygame.display.update()
MAINCLOCK.tick(FPS)
checkForQuit()
def drawBoard(board):
# Draw background of board.
DISPLAYSURF.blit(BGIMAGE, BGIMAGE.get_rect())
# Draw grid lines of the board.
for x in range(BOARDWIDTH + 1):
# Draw the horizontal lines.
startx = (x * SPACESIZE) + XMARGIN
starty = YMARGIN
endx = (x * SPACESIZE) + XMARGIN
endy = YMARGIN + (BOARDHEIGHT * SPACESIZE)
pygame.draw.line(DISPLAYSURF, GRIDLINECOLOR, (startx, starty), (endx, endy))
for y in range(BOARDHEIGHT + 1):
# Draw the vertical lines.
startx = XMARGIN
starty = (y * SPACESIZE) + YMARGIN
endx = XMARGIN + (BOARDWIDTH * SPACESIZE)
endy = (y * SPACESIZE) + YMARGIN
pygame.draw.line(DISPLAYSURF, GRIDLINECOLOR, (startx, starty), (endx, endy))
# Draw the black & white tiles or hint spots.
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
centerx, centery = translateBoardToPixelCoord(x, y)
if board[x][y] == WHITE_TILE or board[x][y] == BLACK_TILE:
if board[x][y] == WHITE_TILE:
tileColor = WHITE
else:
tileColor = BLACK
pygame.draw.circle(DISPLAYSURF, tileColor, (centerx, centery), int(SPACESIZE / 2) - 4)
if board[x][y] == HINT_TILE:
pygame.draw.rect(DISPLAYSURF, HINTCOLOR, (centerx - 4, centery - 4, 8, 8))
def getSpaceClicked(mousex, mousey):
# Return a tuple of two integers of the board space coordinates where
# the mouse was clicked. (Or returns None not in any space.)
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
if mousex > x * SPACESIZE + XMARGIN and \
mousex < (x + 1) * SPACESIZE + XMARGIN and \
mousey > y * SPACESIZE + YMARGIN and \
mousey < (y + 1) * SPACESIZE + YMARGIN:
return (x, y)
return None
def drawInfo(board, playerTile, computerTile, turn):
# Draws scores and whose turn it is at the bottom of the screen.
scores = getScoreOfBoard(board)
scoreSurf = FONT.render("Player Score: %s Computer Score: %s %s's Turn" % (str(scores[playerTile]), str(scores[computerTile]), turn.title()), True, TEXTCOLOR)
scoreRect = scoreSurf.get_rect()
scoreRect.bottomleft = (10, WINDOWHEIGHT - 5)
DISPLAYSURF.blit(scoreSurf, scoreRect)
def resetBoard(board):
# Blanks out the board it is passed, and sets up starting tiles.
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
board[x][y] = EMPTY_SPACE
# Add starting pieces to the center
board[3][3] = WHITE_TILE
board[3][4] = BLACK_TILE
board[4][3] = BLACK_TILE
board[4][4] = WHITE_TILE
def getNewBoard():
# Creates a brand new, empty board data structure.
board = []
for i in range(BOARDWIDTH):
board.append([EMPTY_SPACE] * BOARDHEIGHT)
return board
def isValidMove(board, tile, xstart, ystart):
# Returns False if the player's move is invalid. If it is a valid
# move, returns a list of spaces of the captured pieces.
if board[xstart][ystart] != EMPTY_SPACE or not isOnBoard(xstart, ystart):
return False
board[xstart][ystart] = tile # temporarily set the tile on the board.
if tile == WHITE_TILE:
otherTile = BLACK_TILE
else:
otherTile = WHITE_TILE
tilesToFlip = []
# check each of the eight directions:
for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]:
x, y = xstart, ystart
x += xdirection
y += ydirection
if isOnBoard(x, y) and board[x][y] == otherTile:
# The piece belongs to the other player next to our piece.
x += xdirection
y += ydirection
if not isOnBoard(x, y):
continue
while board[x][y] == otherTile:
x += xdirection
y += ydirection
if not isOnBoard(x, y):
break # break out of while loop, continue in for loop
if not isOnBoard(x, y):
continue
if board[x][y] == tile:
# There are pieces to flip over. Go in the reverse
# direction until we reach the original space, noting all
# the tiles along the way.
while True:
x -= xdirection
y -= ydirection
if x == xstart and y == ystart:
break
tilesToFlip.append([x, y])
board[xstart][ystart] = EMPTY_SPACE # make space empty
if len(tilesToFlip) == 0: # If no tiles flipped, this move is invalid
return False
return tilesToFlip
def isOnBoard(x, y):
# Returns True if the coordinates are located on the board.
return x >= 0 and x < BOARDWIDTH and y >= 0 and y < BOARDHEIGHT
def getBoardWithValidMoves(board, tile):
# Returns a new board with hint markings.
dupeBoard = copy.deepcopy(board)
for x, y in getValidMoves(dupeBoard, tile):
dupeBoard[x][y] = HINT_TILE
return dupeBoard
def getValidMoves(board, tile):
# Returns a list of (x,y) tuples of all valid moves.
validMoves = []
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
if isValidMove(board, tile, x, y) != False:
validMoves.append((x, y))
return validMoves
def getScoreOfBoard(board):
# Determine the score by counting the tiles.
xscore = 0
oscore = 0
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
if board[x][y] == WHITE_TILE:
xscore += 1
if board[x][y] == BLACK_TILE:
oscore += 1
return {WHITE_TILE:xscore, BLACK_TILE:oscore}
def enterPlayerTile():
# Draws the text and handles the mouse click events for letting
# the player choose which color they want to be. Returns
# [WHITE_TILE, BLACK_TILE] if the player chooses to be White,
# [BLACK_TILE, WHITE_TILE] if Black.
# Create the text.
textSurf = FONT.render('Do you want to be white or black?', True, TEXTCOLOR, TEXTBGCOLOR1)
textRect = textSurf.get_rect()
textRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))
xSurf = BIGFONT.render('White', True, TEXTCOLOR, TEXTBGCOLOR1)
xRect = xSurf.get_rect()
xRect.center = (int(WINDOWWIDTH / 2) - 60, int(WINDOWHEIGHT / 2) + 40)
oSurf = BIGFONT.render('Black', True, TEXTCOLOR, TEXTBGCOLOR1)
oRect = oSurf.get_rect()
oRect.center = (int(WINDOWWIDTH / 2) + 60, int(WINDOWHEIGHT / 2) + 40)
while True:
# Keep looping until the player has clicked on a color.
checkForQuit()
for event in pygame.event.get(): # event handling loop
if event.type == MOUSEBUTTONUP:
mousex, mousey = event.pos
if xRect.collidepoint( (mousex, mousey) ):
return [WHITE_TILE, BLACK_TILE]
elif oRect.collidepoint( (mousex, mousey) ):
return [BLACK_TILE, WHITE_TILE]
# Draw the screen.
DISPLAYSURF.blit(textSurf, textRect)
DISPLAYSURF.blit(xSurf, xRect)
DISPLAYSURF.blit(oSurf, oRect)
pygame.display.update()
MAINCLOCK.tick(FPS)
def makeMove(board, tile, xstart, ystart, realMove=False):
# Place the tile on the board at xstart, ystart, and flip tiles
# Returns False if this is an invalid move, True if it is valid.
tilesToFlip = isValidMove(board, tile, xstart, ystart)
if tilesToFlip == False:
return False
board[xstart][ystart] = tile
if realMove:
animateTileChange(tilesToFlip, tile, (xstart, ystart))
for x, y in tilesToFlip:
board[x][y] = tile
return True
def isOnCorner(x, y):
# Returns True if the position is in one of the four corners.
return (x == 0 and y == 0) or \
(x == BOARDWIDTH and y == 0) or \
(x == 0 and y == BOARDHEIGHT) or \
(x == BOARDWIDTH and y == BOARDHEIGHT)
def getComputerMove(board, computerTile):
# Given a board and the computer's tile, determine where to
# move and return that move as a [x, y] list.
possibleMoves = getValidMoves(board, computerTile)
# randomize the order of the possible moves
random.shuffle(possibleMoves)
# always go for a corner if available.
for x, y in possibleMoves:
if isOnCorner(x, y):
return [x, y]
# Go through all possible moves and remember the best scoring move
bestScore = -1
for x, y in possibleMoves:
dupeBoard = copy.deepcopy(board)
makeMove(dupeBoard, computerTile, x, y)
score = getScoreOfBoard(dupeBoard)[computerTile]
if score > bestScore:
bestMove = [x, y]
bestScore = score
return bestMove
def checkForQuit():
for event in pygame.event.get((QUIT, KEYUP)): # event handling loop
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
if __name__ == '__main__':
main()
잉크 유출, "플러드 잇" 복제본.
"플러드 잇" 게임은 색 타일로 가득 찬 보드에서 시작됩니다. 각 라운드에서 플레이어는 새로운 색을 선택하여 왼쪽 상단 모서리에 있는 타일과 같은 색의 인접한 타일을 칠합니다. 이 게임은 플러드 채우기 알고리즘을 사용합니다. 게임의 목표는 모든 라운드가 사용된 후 보드 전체를 하나의 색상으로 바꾸는 것입니다.
이 게임에는 플레이어가 보드의 크기와 게임의 난이도를 변경할 수 있는 설정 화면도 있습니다. 플레이어가 색상에 지루함을 느낀다면 다른 색 구성표로 전환할 수도 있습니다.
잉크 유출 소스 코드
이 소스 코드는 invpy.com/inkspill.py 다운로드할 수 있습니다.
Flippy에서 사용하는 이미지 파일은 invpy.com/inkspillimages.zip 다운로드할 수 있습니다.
# Ink Spill (a Flood It clone)
# http://.com/pygame
# By Al Sweigart [[email protected]](/cdn-cgi/l/email-protection)
# Released under a "Simplified BSD" license
import random, sys, webbrowser, copy, pygame
from pygame.locals import *
# There are different box sizes, number of boxes, and
# life depending on the "board size" setting selected.
SMALLBOXSIZE = 60 # size is in pixels
MEDIUMBOXSIZE = 20
LARGEBOXSIZE = 11
SMALLBOARDSIZE = 6 # size is in boxes
MEDIUMBOARDSIZE = 17
LARGEBOARDSIZE = 30
SMALLMAXLIFE = 10 # number of turns
MEDIUMMAXLIFE = 30
LARGEMAXLIFE = 64
FPS = 30
WINDOWWIDTH = 640
WINDOWHEIGHT = 480
boxSize = MEDIUMBOXSIZE
PALETTEGAPSIZE = 10
PALETTESIZE = 45
EASY = 0 # arbitrary but unique value
MEDIUM = 1 # arbitrary but unique value
HARD = 2 # arbitrary but unique value
difficulty = MEDIUM # game starts in "medium" mode
maxLife = MEDIUMMAXLIFE
boardWidth = MEDIUMBOARDSIZE
boardHeight = MEDIUMBOARDSIZE
# R G B
WHITE = (255, 255, 255)
DARKGRAY = ( 70, 70, 70)
BLACK = ( 0, 0, 0)
RED = (255, 0, 0)
GREEN = ( 0, 255, 0)
BLUE = ( 0, 0, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 128, 0)
PURPLE = (255, 0, 255)
# The first color in each scheme is the background color, the next six are the palette colors.
COLORSCHEMES = (((150, 200, 255), RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE),
((0, 155, 104), (97, 215, 164), (228, 0, 69), (0, 125, 50), (204, 246, 0), (148, 0, 45), (241, 109, 149)),
((195, 179, 0), (255, 239, 115), (255, 226, 0), (147, 3, 167), (24, 38, 176), (166, 147, 0), (197, 97, 211)),
((85, 0, 0), (155, 39, 102), (0, 201, 13), (255, 118, 0), (206, 0, 113), (0, 130, 9), (255, 180, 115)),
((191, 159, 64), (183, 182, 208), (4, 31, 183), (167, 184, 45), (122, 128, 212), (37, 204, 7), (88, 155, 213)),
((200, 33, 205), (116, 252, 185), (68, 56, 56), (52, 238, 83), (23, 149, 195), (222, 157, 227), (212, 86, 185)))
for i in range(len(COLORSCHEMES)):
assert len(COLORSCHEMES[i]) == 7, 'Color scheme %s does not have exactly 7 colors.' % (i)
bgColor = COLORSCHEMES[0][0]
paletteColors = COLORSCHEMES[0][1:]
def main():
global FPSCLOCK, DISPLAYSURF, LOGOIMAGE, SPOTIMAGE, SETTINGSIMAGE, SETTINGSBUTTONIMAGE, RESETBUTTONIMAGE
pygame.init()
FPSCLOCK = pygame.time.Clock()
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
# Load images
LOGOIMAGE = pygame.image.load('inkspilllogo.png')
SPOTIMAGE = pygame.image.load('inkspillspot.png')
SETTINGSIMAGE = pygame.image.load('inkspillsettings.png')
SETTINGSBUTTONIMAGE = pygame.image.load('inkspillsettingsbutton.png')
RESETBUTTONIMAGE = pygame.image.load('inkspillresetbutton.png')
pygame.display.set_caption('Ink Spill')
mousex = 0
mousey = 0
mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty)
life = maxLife
lastPaletteClicked = None
while True: # main game loop
paletteClicked = None
resetGame = False
# Draw the screen.
DISPLAYSURF.fill(bgColor)
drawLogoAndButtons()
drawBoard(mainBoard)
drawLifeMeter(life)
drawPalettes()
checkForQuit()
for event in pygame.event.get(): # event handling loop
if event.type == MOUSEBUTTONUP:
mousex, mousey = event.pos
if pygame.Rect(WINDOWWIDTH - SETTINGSBUTTONIMAGE.get_width(),
WINDOWHEIGHT - SETTINGSBUTTONIMAGE.get_height(),
SETTINGSBUTTONIMAGE.get_width(),
SETTINGSBUTTONIMAGE.get_height()).collidepoint(mousex, mousey):
resetGame = showSettingsScreen() # clicked on Settings button
elif pygame.Rect(WINDOWWIDTH - RESETBUTTONIMAGE.get_width(),
WINDOWHEIGHT - SETTINGSBUTTONIMAGE.get_height() - RESETBUTTONIMAGE.get_height(),
RESETBUTTONIMAGE.get_width(),
RESETBUTTONIMAGE.get_height()).collidepoint(mousex, mousey):
resetGame = True # clicked on Reset button
else:
# check if a palette button was clicked
paletteClicked = getColorOfPaletteAt(mousex, mousey)
if paletteClicked != None and paletteClicked != lastPaletteClicked:
# a palette button was clicked that is different from the
# last palette button clicked (this check prevents the player
# from accidentally clicking the same palette twice)
lastPaletteClicked = paletteClicked
floodAnimation(mainBoard, paletteClicked)
life -= 1
resetGame = False
if hasWon(mainBoard):
for i in range(4): # flash border 4 times
flashBorderAnimation(WHITE, mainBoard)
resetGame = True
pygame.time.wait(2000) # pause so the player can bask in victory
elif life == 0:
# life is zero, so player has lost
drawLifeMeter(0)
pygame.display.update()
pygame.time.wait(400)
for i in range(4):
flashBorderAnimation(BLACK, mainBoard)
resetGame = True
pygame.time.wait(2000) # pause so the player can suffer in their defeat
if resetGame:
# start a new game
mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty)
life = maxLife
lastPaletteClicked = None
pygame.display.update()
FPSCLOCK.tick(FPS)
def checkForQuit():
# Terminates the program if there are any QUIT or escape key events.
for event in pygame.event.get(QUIT): # get all the QUIT events
pygame.quit() # terminate if any QUIT events are present
sys.exit()
for event in pygame.event.get(KEYUP): # get all the KEYUP events
if event.key == K_ESCAPE:
pygame.quit() # terminate if the KEYUP event was for the Esc
sys.exit()
pygame.event.post(event) # put the other KEYUP event objects back
def hasWon(board):
# if the entire board is the same color, player has won
for x in range(boardWidth):
for y in range(boardHeight):
if board[x][y] != board[0][0]:
return False # found a different color, player has not won
return True
def showSettingsScreen():
global difficulty, boxSize, boardWidth, boardHeight, maxLife, paletteColors, bgColor
# The pixel coordinates in this function were obtained by loading
# the inkspillsettings.png image into a graphics editor and reading
# the pixel coordinates from there. Handy trick.
origDifficulty = difficulty
origBoxSize = boxSize
screenNeedsRedraw = True
while True:
if screenNeedsRedraw:
DISPLAYSURF.fill(bgColor)
DISPLAYSURF.blit(SETTINGSIMAGE, (0,0))
# place the ink spot marker next to the selected difficulty
if difficulty == EASY:
DISPLAYSURF.blit(SPOTIMAGE, (30, 4))
if difficulty == MEDIUM:
DISPLAYSURF.blit(SPOTIMAGE, (8, 41))
if difficulty == HARD:
DISPLAYSURF.blit(SPOTIMAGE, (30, 76))
# place the ink spot marker next to the selected size
if boxSize == SMALLBOXSIZE:
DISPLAYSURF.blit(SPOTIMAGE, (22, 150))
if boxSize == MEDIUMBOXSIZE:
DISPLAYSURF.blit(SPOTIMAGE, (11, 185))
if boxSize == LARGEBOXSIZE:
DISPLAYSURF.blit(SPOTIMAGE, (24, 220))
for i in range(len(COLORSCHEMES)):
drawColorSchemeBoxes(500, i * 60 + 30, i)
pygame.display.update()
screenNeedsRedraw = False # by default, don't redraw the screen
for event in pygame.event.get(): # event handling loop
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYUP:
if event.key == K_ESCAPE:
# Esc key on settings screen goes back to game
return not (origDifficulty == difficulty and origBoxSize == boxSize)
elif event.type == MOUSEBUTTONUP:
screenNeedsRedraw = True # screen should be redrawn
mousex, mousey = event.pos # syntactic sugar
# check for clicks on the difficulty buttons
if pygame.Rect(74, 16, 111, 30).collidepoint(mousex, mousey):
difficulty = EASY
elif pygame.Rect(53, 50, 104, 29).collidepoint(mousex, mousey):
difficulty = MEDIUM
elif pygame.Rect(72, 85, 65, 31).collidepoint(mousex, mousey):
difficulty = HARD
# check for clicks on the size buttons
elif pygame.Rect(63, 156, 84, 31).collidepoint(mousex, mousey):
# small board size setting:
boxSize = SMALLBOXSIZE
boardWidth = SMALLBOARDSIZE
boardHeight = SMALLBOARDSIZE
maxLife = SMALLMAXLIFE
elif pygame.Rect(52, 192, 106,32).collidepoint(mousex, mousey):
# medium board size setting:
boxSize = MEDIUMBOXSIZE
boardWidth = MEDIUMBOARDSIZE
boardHeight = MEDIUMBOARDSIZE
maxLife = MEDIUMMAXLIFE
elif pygame.Rect(67, 228, 58, 37).collidepoint(mousex, mousey):
# large board size setting:
boxSize = LARGEBOXSIZE
boardWidth = LARGEBOARDSIZE
boardHeight = LARGEBOARDSIZE
maxLife = LARGEMAXLIFE
elif pygame.Rect(14, 299, 371, 97).collidepoint(mousex, mousey):
# clicked on the "learn programming" ad
webbrowser.open('http://.com') # opens a web browser
elif pygame.Rect(178, 418, 215, 34).collidepoint(mousex, mousey):
# clicked on the "back to game" button
return not (origDifficulty == difficulty and origBoxSize == boxSize)
for i in range(len(COLORSCHEMES)):
# clicked on a color scheme button
if pygame.Rect(500, 30 + i * 60, MEDIUMBOXSIZE * 3, MEDIUMBOXSIZE * 2).collidepoint(mousex, mousey):
bgColor = COLORSCHEMES[i][0]
paletteColors = COLORSCHEMES[i][1:]
def drawColorSchemeBoxes(x, y, schemeNum):
# Draws the color scheme boxes that appear on the "Settings" screen.
for boxy in range(2):
for boxx in range(3):
pygame.draw.rect(DISPLAYSURF, COLORSCHEMES[schemeNum][3 * boxy + boxx + 1], (x + MEDIUMBOXSIZE * boxx, y + MEDIUMBOXSIZE * boxy, MEDIUMBOXSIZE, MEDIUMBOXSIZE))
if paletteColors == COLORSCHEMES[schemeNum][1:]:
# put the ink spot next to the selected color scheme
DISPLAYSURF.blit(SPOTIMAGE, (x - 50, y))
def flashBorderAnimation(color, board, animationSpeed=30):
origSurf = DISPLAYSURF.copy()
flashSurf = pygame.Surface(DISPLAYSURF.get_size())
flashSurf = flashSurf.convert_alpha()
for start, end, step in ((0, 256, 1), (255, 0, -1)):
# the first iteration on the outer loop will set the inner loop
# to have transparency go from 0 to 255, the second iteration will
# have it go from 255 to 0\. This is the "flash".
for transparency in range(start, end, animationSpeed * step):
DISPLAYSURF.blit(origSurf, (0, 0))
r, g, b = color
flashSurf.fill((r, g, b, transparency))
DISPLAYSURF.blit(flashSurf, (0, 0))
drawBoard(board) # draw board ON TOP OF the transparency layer
pygame.display.update()
FPSCLOCK.tick(FPS)
DISPLAYSURF.blit(origSurf, (0, 0)) # redraw the original surface
def floodAnimation(board, paletteClicked, animationSpeed=25):
origBoard = copy.deepcopy(board)
floodFill(board, board[0][0], paletteClicked, 0, 0)
for transparency in range(0, 255, animationSpeed):
# The "new" board slowly become opaque over the original board.
drawBoard(origBoard)
drawBoard(board, transparency)
pygame.display.update()
FPSCLOCK.tick(FPS)
def generateRandomBoard(width, height, difficulty=MEDIUM):
# Creates a board data structure with random colors for each box.
board = []
for x in range(width):
column = []
for y in range(height):
column.append(random.randint(0, len(paletteColors) - 1))
board.append(column)
# Make board easier by setting some boxes to same color as a neighbor.
# Determine how many boxes to change.
if difficulty == EASY:
if boxSize == SMALLBOXSIZE:
boxesToChange = 100
else:
boxesToChange = 1500
elif difficulty == MEDIUM:
if boxSize == SMALLBOXSIZE:
boxesToChange = 5
else:
boxesToChange = 200
else:
boxesToChange = 0
# Change neighbor's colors:
for i in range(boxesToChange):
# Randomly choose a box whose color to copy
x = random.randint(1, width-2)
y = random.randint(1, height-2)
# Randomly choose neighbors to change.
direction = random.randint(0, 3)
if direction == 0: # change left and up neighbor
board[x-1][y] = board[x][y]
board[x][y-1] = board[x][y]
elif direction == 1: # change right and down neighbor
board[x+1][y] = board[x][y]
board[x][y+1] = board[x][y]
elif direction == 2: # change right and up neighbor
board[x][y-1] = board[x][y]
board[x+1][y] = board[x][y]
else: # change left and down neighbor
board[x][y+1] = board[x][y]
board[x-1][y] = board[x][y]
return board
def drawLogoAndButtons():
# draw the Ink Spill logo and Settings and Reset buttons.
DISPLAYSURF.blit(LOGOIMAGE, (WINDOWWIDTH - LOGOIMAGE.get_width(), 0))
DISPLAYSURF.blit(SETTINGSBUTTONIMAGE, (WINDOWWIDTH - SETTINGSBUTTONIMAGE.get_width(), WINDOWHEIGHT - SETTINGSBUTTONIMAGE.get_height()))
DISPLAYSURF.blit(RESETBUTTONIMAGE, (WINDOWWIDTH - RESETBUTTONIMAGE.get_width(), WINDOWHEIGHT - SETTINGSBUTTONIMAGE.get_height() - RESETBUTTONIMAGE.get_height()))
def drawBoard(board, transparency=255):
# The colored squares are drawn to a temporary surface which is then
# drawn to the DISPLAYSURF surface. This is done so we can draw the
# squares with transparency on top of DISPLAYSURF as it currently is.
tempSurf = pygame.Surface(DISPLAYSURF.get_size())
tempSurf = tempSurf.convert_alpha()
tempSurf.fill((0, 0, 0, 0))
for x in range(boardWidth):
for y in range(boardHeight):
left, top = leftTopPixelCoordOfBox(x, y)
r, g, b = paletteColors[board[x][y]]
pygame.draw.rect(tempSurf, (r, g, b, transparency), (left, top, boxSize, boxSize))
left, top = leftTopPixelCoordOfBox(0, 0)
pygame.draw.rect(tempSurf, BLACK, (left-1, top-1, boxSize * boardWidth + 1, boxSize * boardHeight + 1), 1)
DISPLAYSURF.blit(tempSurf, (0, 0))
def drawPalettes():
# Draws the six color palettes at the bottom of the screen.
numColors = len(paletteColors)
xmargin = int((WINDOWWIDTH - ((PALETTESIZE * numColors) + (PALETTEGAPSIZE * (numColors - 1)))) / 2)
for i in range(numColors):
left = xmargin + (i * PALETTESIZE) + (i * PALETTEGAPSIZE)
top = WINDOWHEIGHT - PALETTESIZE - 10
pygame.draw.rect(DISPLAYSURF, paletteColors[i], (left, top, PALETTESIZE, PALETTESIZE))
pygame.draw.rect(DISPLAYSURF, bgColor, (left + 2, top + 2, PALETTESIZE - 4, PALETTESIZE - 4), 2)
def drawLifeMeter(currentLife):
lifeBoxSize = int((WINDOWHEIGHT - 40) / maxLife)
# Draw background color of life meter.
pygame.draw.rect(DISPLAYSURF, bgColor, (20, 20, 20, 20 + (maxLife * lifeBoxSize)))
for i in range(maxLife):
if currentLife >= (maxLife - i): # draw a solid red box
pygame.draw.rect(DISPLAYSURF, RED, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize))
pygame.draw.rect(DISPLAYSURF, WHITE, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize), 1) # draw white outline
def getColorOfPaletteAt(x, y):
# Returns the index of the color in paletteColors that the x and y parameters
# are over. Returns None if x and y are not over any palette.
numColors = len(paletteColors)
xmargin = int((WINDOWWIDTH - ((PALETTESIZE * numColors) + (PALETTEGAPSIZE * (numColors - 1)))) / 2)
top = WINDOWHEIGHT - PALETTESIZE - 10
for i in range(numColors):
# Find out if the mouse click is inside any of the palettes.
left = xmargin + (i * PALETTESIZE) + (i * PALETTEGAPSIZE)
r = pygame.Rect(left, top, PALETTESIZE, PALETTESIZE)
if r.collidepoint(x, y):
return i
return None # no palette exists at these x, y coordinates
def floodFill(board, oldColor, newColor, x, y):
# This is the flood fill algorithm.
if oldColor == newColor or board[x][y] != oldColor:
return
board[x][y] = newColor # change the color of the current box
# Make the recursive call for any neighboring boxes:
if x > 0:
floodFill(board, oldColor, newColor, x - 1, y) # on box to the left
if x < boardWidth - 1:
floodFill(board, oldColor, newColor, x + 1, y) # on box to the right
if y > 0:
floodFill(board, oldColor, newColor, x, y - 1) # on box to up
if y < boardHeight - 1:
floodFill(board, oldColor, newColor, x, y + 1) # on box to down
def leftTopPixelCoordOfBox(boxx, boxy):
# Returns the x and y of the left-topmost pixel of the xth & yth box.
xmargin = int((WINDOWWIDTH - (boardWidth * boxSize)) / 2)
ymargin = int((WINDOWHEIGHT - (boardHeight * boxSize)) / 2)
return (boxx * boxSize + xmargin, boxy * boxSize + ymargin)
if __name__ == '__main__':
main()
네쌍둥이. "네쌍둥이" 복제본입니다.
"테트리스" 게임에는 7x6 보드가 있으며 플레이어는 보드 상단에서 교대로 마커를 놓습니다. 마커는 각 열의 상단에서 떨어지며 해당 열의 하단 또는 상단 마커에 놓이게 됩니다. 네 개의 마커가 가로, 세로 또는 대각선으로 정렬되면 플레이어가 승리합니다.
이 게임의 AI는 훌륭합니다. 컴퓨터는 가능한 모든 수를 모델링한 다음, 각 수에 대한 인간 플레이어의 반응을 모델링하고, 가능한 모든 수를 모델링한 다음, 각 수에 대한 인간 플레이어의 반응을 모델링했습니다! 이 모든 생각 끝에 컴퓨터는 승리할 가능성이 가장 높은 수를 결정합니다.
그래서 컴퓨터는 이기기 어렵습니다. 저는 보통 컴퓨터에게 집니다.
자신의 차례에 7개의 가능한 수를 둘 수 있고 상대방은 7개의 가능한 수를 둘 수 있으며, 7개의 가능한 수를 둘 수 있는 상대방은 7개의 가능한 수를 둘 수 있으므로 각 턴에서 컴퓨터는 7 x 7 x 7 x 7 = 2,401개의 가능한 수를 고려한다는 의미입니다. 난이도 상수를 더 큰 숫자로 설정하면 컴퓨터가 게임을 더 고려하도록 할 수 있지만, 그보다 큰 값으로 설정하면 컴퓨터가 자신의 차례를 계산하는 데 시간이 오래 걸립니다.
난이도를 '보통'으로 설정하여 컴퓨터의 난이도를 낮출 수도 있습니다. 그러면 컴퓨터는 각 단계와 해당 단계에 대한 플레이어의 가능한 반응만 고려합니다. 난이도를 0으로 설정하면 컴퓨터는 모든 지능을 잃고 무작위로만 움직입니다.
네 아들의 진주 소스 코드
이 소스 코드는 invpy.com/fourinarow.py 다운로드할 수 있습니다.
Flippy에서 사용하는 이미지 파일은 invpy.com/fourinarowimages.zip 다운로드할 수 있습니다.
# Four-In-A-Row (a Connect Four clone)
# By Al Sweigart [[email protected]](/cdn-cgi/l/email-protection)
# http://.com/pygame
# Released under a "Simplified BSD" license
import random, copy, sys, pygame
from pygame.locals import *
BOARDWIDTH = 7 # how many spaces wide the board is
BOARDHEIGHT = 6 # how many spaces tall the board is
assert BOARDWIDTH >= 4 and BOARDHEIGHT >= 4, 'Board must be at least 4x4.'
DIFFICULTY = 2 # how many moves to look ahead. (>2 is usually too slow)
SPACESIZE = 50 # size of the tokens and individual board spaces in pixels
FPS = 30 # frames per second to update the screen
WINDOWWIDTH = 640 # width of the program's window, in pixels
WINDOWHEIGHT = 480 # height in pixels
XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * SPACESIZE) / 2)
YMARGIN = int((WINDOWHEIGHT - BOARDHEIGHT * SPACESIZE) / 2)
BRIGHTBLUE = (0, 50, 255)
WHITE = (255, 255, 255)
BGCOLOR = BRIGHTBLUE
TEXTCOLOR = WHITE
RED = 'red'
BLACK = 'black'
EMPTY = None
HUMAN = 'human'
COMPUTER = 'computer'
def main():
global FPSCLOCK, DISPLAYSURF, REDPILERECT, BLACKPILERECT, REDTOKENIMG
global BLACKTOKENIMG, BOARDIMG, ARROWIMG, ARROWRECT, HUMANWINNERIMG
global COMPUTERWINNERIMG, WINNERRECT, TIEWINNERIMG
pygame.init()
FPSCLOCK = pygame.time.Clock()
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
pygame.display.set_caption('Four in a Row')
REDPILERECT = pygame.Rect(int(SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE)
BLACKPILERECT = pygame.Rect(WINDOWWIDTH - int(3 * SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE)
REDTOKENIMG = pygame.image.load('4row_red.png')
REDTOKENIMG = pygame.transform.smoothscale(REDTOKENIMG, (SPACESIZE, SPACESIZE))
BLACKTOKENIMG = pygame.image.load('4row_black.png')
BLACKTOKENIMG = pygame.transform.smoothscale(BLACKTOKENIMG, (SPACESIZE, SPACESIZE))
BOARDIMG = pygame.image.load('4row_board.png')
BOARDIMG = pygame.transform.smoothscale(BOARDIMG, (SPACESIZE, SPACESIZE))
HUMANWINNERIMG = pygame.image.load('4row_humanwinner.png')
COMPUTERWINNERIMG = pygame.image.load('4row_computerwinner.png')
TIEWINNERIMG = pygame.image.load('4row_tie.png')
WINNERRECT = HUMANWINNERIMG.get_rect()
WINNERRECT.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))
ARROWIMG = pygame.image.load('4row_arrow.png')
ARROWRECT = ARROWIMG.get_rect()
ARROWRECT.left = REDPILERECT.right + 10
ARROWRECT.centery = REDPILERECT.centery
isFirstGame = True
while True:
runGame(isFirstGame)
isFirstGame = False
def runGame(isFirstGame):
if isFirstGame:
# Let the computer go first on the first game, so the player
# can see how the tokens are dragged from the token piles.
turn = COMPUTER
showHelp = True
else:
# Randomly choose who goes first.
if random.randint(0, 1) == 0:
turn = COMPUTER
else:
turn = HUMAN
showHelp = False
# Set up a blank board data structure.
mainBoard = getNewBoard()
while True: # main game loop
if turn == HUMAN:
# Human player's turn.
getHumanMove(mainBoard, showHelp)
if showHelp:
# turn off help arrow after the first move
showHelp = False
if isWinner(mainBoard, RED):
winnerImg = HUMANWINNERIMG
break
turn = COMPUTER # switch to other player's turn
else:
# Computer player's turn.
column = getComputerMove(mainBoard)
animateComputerMoving(mainBoard, column)
makeMove(mainBoard, BLACK, column)
if isWinner(mainBoard, BLACK):
winnerImg = COMPUTERWINNERIMG
break
turn = HUMAN # switch to other player's turn
if isBoardFull(mainBoard):
# A completely filled board means it's a tie.
winnerImg = TIEWINNERIMG
break
while True:
# Keep looping until player clicks the mouse or quits.
drawBoard(mainBoard)
DISPLAYSURF.blit(winnerImg, WINNERRECT)
pygame.display.update()
FPSCLOCK.tick()
for event in pygame.event.get(): # event handling loop
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
elif event.type == MOUSEBUTTONUP:
return
def makeMove(board, player, column):
lowest = getLowestEmptySpace(board, column)
if lowest != -1:
board[column][lowest] = player
def drawBoard(board, extraToken=None):
DISPLAYSURF.fill(BGCOLOR)
# draw tokens
spaceRect = pygame.Rect(0, 0, SPACESIZE, SPACESIZE)
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE))
if board[x][y] == RED:
DISPLAYSURF.blit(REDTOKENIMG, spaceRect)
elif board[x][y] == BLACK:
DISPLAYSURF.blit(BLACKTOKENIMG, spaceRect)
# draw the extra token
if extraToken != None:
if extraToken['color'] == RED:
DISPLAYSURF.blit(REDTOKENIMG, (extraToken['x'], extraToken['y'], SPACESIZE, SPACESIZE))
elif extraToken['color'] == BLACK:
DISPLAYSURF.blit(BLACKTOKENIMG, (extraToken['x'], extraToken['y'], SPACESIZE, SPACESIZE))
# draw board over the tokens
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE))
DISPLAYSURF.blit(BOARDIMG, spaceRect)
# draw the red and black tokens off to the side
DISPLAYSURF.blit(REDTOKENIMG, REDPILERECT) # red on the left
DISPLAYSURF.blit(BLACKTOKENIMG, BLACKPILERECT) # black on the right
def getNewBoard():
board = []
for x in range(BOARDWIDTH):
board.append([EMPTY] * BOARDHEIGHT)
return board
def getHumanMove(board, isFirstMove):
draggingToken = False
tokenx, tokeny = None, None
while True:
for event in pygame.event.get(): # event handling loop
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == MOUSEBUTTONDOWN and not draggingToken and REDPILERECT.collidepoint(event.pos):
# start of dragging on red token pile.
draggingToken = True
tokenx, tokeny = event.pos
elif event.type == MOUSEMOTION and draggingToken:
# update the position of the red token being dragged
tokenx, tokeny = event.pos
elif event.type == MOUSEBUTTONUP and draggingToken:
# let go of the token being dragged
if tokeny < YMARGIN and tokenx > XMARGIN and tokenx < WINDOWWIDTH - XMARGIN:
# let go at the top of the screen.
column = int((tokenx - XMARGIN) / SPACESIZE)
if isValidMove(board, column):
animateDroppingToken(board, column, RED)
board[column][getLowestEmptySpace(board, column)] = RED
drawBoard(board)
pygame.display.update()
return
tokenx, tokeny = None, None
draggingToken = False
if tokenx != None and tokeny != None:
drawBoard(board, {'x':tokenx - int(SPACESIZE / 2), 'y':tokeny - int(SPACESIZE / 2), 'color':RED})
else:
drawBoard(board)
if isFirstMove:
# Show the help arrow for the player's first move.
DISPLAYSURF.blit(ARROWIMG, ARROWRECT)
pygame.display.update()
FPSCLOCK.tick()
def animateDroppingToken(board, column, color):
x = XMARGIN + column * SPACESIZE
y = YMARGIN - SPACESIZE
dropSpeed = 1.0
lowestEmptySpace = getLowestEmptySpace(board, column)
while True:
y += int(dropSpeed)
dropSpeed += 0.5
if int((y - YMARGIN) / SPACESIZE) >= lowestEmptySpace:
return
drawBoard(board, {'x':x, 'y':y, 'color':color})
pygame.display.update()
FPSCLOCK.tick()
def animateComputerMoving(board, column):
x = BLACKPILERECT.left
y = BLACKPILERECT.top
speed = 1.0
# moving the black tile up
while y > (YMARGIN - SPACESIZE):
y -= int(speed)
speed += 0.5
drawBoard(board, {'x':x, 'y':y, 'color':BLACK})
pygame.display.update()
FPSCLOCK.tick()
# moving the black tile over
y = YMARGIN - SPACESIZE
speed = 1.0
while x > (XMARGIN + column * SPACESIZE):
x -= int(speed)
speed += 0.5
drawBoard(board, {'x':x, 'y':y, 'color':BLACK})
pygame.display.update()
FPSCLOCK.tick()
# dropping the black tile
animateDroppingToken(board, column, BLACK)
def getComputerMove(board):
potentialMoves = getPotentialMoves(board, BLACK, DIFFICULTY)
# get the best fitness from the potential moves
bestMoveFitness = -1
for i in range(BOARDWIDTH):
if potentialMoves[i] > bestMoveFitness and isValidMove(board, i):
bestMoveFitness = potentialMoves[i]
# find all potential moves that have this best fitness
bestMoves = []
for i in range(len(potentialMoves)):
if potentialMoves[i] == bestMoveFitness and isValidMove(board, i):
bestMoves.append(i)
return random.choice(bestMoves)
def getPotentialMoves(board, tile, lookAhead):
if lookAhead == 0 or isBoardFull(board):
return [0] * BOARDWIDTH
if tile == RED:
enemyTile = BLACK
else:
enemyTile = RED
# Figure out the best move to make.
potentialMoves = [0] * BOARDWIDTH
for firstMove in range(BOARDWIDTH):
dupeBoard = copy.deepcopy(board)
if not isValidMove(dupeBoard, firstMove):
continue
makeMove(dupeBoard, tile, firstMove)
if isWinner(dupeBoard, tile):
# a winning move automatically gets a perfect fitness
potentialMoves[firstMove] = 1
break # don't bother calculating other moves
else:
# do other player's counter moves and determine best one
if isBoardFull(dupeBoard):
potentialMoves[firstMove] = 0
else:
for counterMove in range(BOARDWIDTH):
dupeBoard2 = copy.deepcopy(dupeBoard)
if not isValidMove(dupeBoard2, counterMove):
continue
makeMove(dupeBoard2, enemyTile, counterMove)
if isWinner(dupeBoard2, enemyTile):
# a losing move automatically gets the worst fitness
potentialMoves[firstMove] = -1
break
else:
# do the recursive call to getPotentialMoves()
results = getPotentialMoves(dupeBoard2, tile, lookAhead - 1)
potentialMoves[firstMove] += (sum(results) / BOARDWIDTH) / BOARDWIDTH
return potentialMoves
def getLowestEmptySpace(board, column):
# Return the row number of the lowest empty row in the given column.
for y in range(BOARDHEIGHT-1, -1, -1):
if board[column][y] == EMPTY:
return y
return -1
def isValidMove(board, column):
# Returns True if there is an empty space in the given column.
# Otherwise returns False.
if column < 0 or column >= (BOARDWIDTH) or board[column][0] != EMPTY:
return False
return True
def isBoardFull(board):
# Returns True if there are no empty spaces anywhere on the board.
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
if board[x][y] == EMPTY:
return False
return True
def isWinner(board, tile):
# check horizontal spaces
for x in range(BOARDWIDTH - 3):
for y in range(BOARDHEIGHT):
if board[x][y] == tile and board[x+1][y] == tile and board[x+2][y] == tile and board[x+3][y] == tile:
return True
# check vertical spaces
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT - 3):
if board[x][y] == tile and board[x][y+1] == tile and board[x][y+2] == tile and board[x][y+3] == tile:
return True
# check / diagonal spaces
for x in range(BOARDWIDTH - 3):
for y in range(3, BOARDHEIGHT):
if board[x][y] == tile and board[x+1][y-1] == tile and board[x+2][y-2] == tile and board[x+3][y-3] == tile:
return True
# check \ diagonal spaces
for x in range(BOARDWIDTH - 3):
for y in range(BOARDHEIGHT - 3):
if board[x][y] == tile and board[x+1][y+1] == tile and board[x+2][y+2] == tile and board[x+3][y+3] == tile:
return True
return False
if __name__ == '__main__':
main()
"보석으로 장식된" 클론, 젬젬.
"보석은 보석을 떨어뜨려 보드를 채우는 게임입니다. 플레이어는 인접한 두 개의 보석을 교환하여 세 개의 보석을 연속으로 맞추려고 시도할 수 있습니다. 일치하는 보석은 사라지고 위에서부터 새로운 보석이 떨어집니다. 세 개 이상의 보석을 맞추거나 보석이 연쇄 반응을 일으키면 더 많은 점수를 획득할 수 있습니다. 플레이어의 점수는 시간이 지남에 따라 서서히 감소하므로 플레이어는 계속해서 새로운 보석을 맞춰야 합니다. 보드에 더 이상 보석을 매치할 수 없게 되면 게임이 종료됩니다.
Gemgem 소스 코드
이 소스 코드는 invpy.com/fourinarowimages.zip 다운로드할 수 있습니다.
# Gemgem (a Bejeweled clone)
# By Al Sweigart [[email protected]](/cdn-cgi/l/email-protection)
# http://.com/pygame
# Released under a "Simplified BSD" license
"""
This program has "gem data structures", which are basically dictionaries
with the following keys:
'x' and 'y' - The location of the gem on the board. 0,0 is the top left.
There is also a ROWABOVEBOARD row that 'y' can be set to,
to indicate that it is above the board.
'direction' - one of the four constant variables UP, DOWN, LEFT, RIGHT.
This is the direction the gem is moving.
'imageNum' - The integer index into GEMIMAGES to denote which image
this gem uses.
"""
import random, time, pygame, sys, copy
from pygame.locals import *
FPS = 30 # frames per second to update the screen
WINDOWWIDTH = 600 # width of the program's window, in pixels
WINDOWHEIGHT = 600 # height in pixels
BOARDWIDTH = 8 # how many columns in the board
BOARDHEIGHT = 8 # how many rows in the board
GEMIMAGESIZE = 64 # width & height of each space in pixels
# NUMGEMIMAGES is the number of gem types. You will need .png image
# files named gem0.png, gem1.png, etc. up to gem(N-1).png.
NUMGEMIMAGES = 7
assert NUMGEMIMAGES >= 5 # game needs at least 5 types of gems to work
# NUMMATCHSOUNDS is the number of different sounds to choose from when
# a match is made. The .wav files are named match0.wav, match1.wav, etc.
NUMMATCHSOUNDS = 6
MOVERATE = 25 # 1 to 100, larger num means faster animations
DEDUCTSPEED = 0.8 # reduces score by 1 point every DEDUCTSPEED seconds.
# R G B
PURPLE = (255, 0, 255)
LIGHTBLUE = (170, 190, 255)
BLUE = ( 0, 0, 255)
RED = (255, 100, 100)
BLACK = ( 0, 0, 0)
BROWN = ( 85, 65, 0)
HIGHLIGHTCOLOR = PURPLE # color of the selected gem's border
BGCOLOR = LIGHTBLUE # background color on the screen
GRIDCOLOR = BLUE # color of the game board
GAMEOVERCOLOR = RED # color of the "Game over" text.
GAMEOVERBGCOLOR = BLACK # background color of the "Game over" text.
SCORECOLOR = BROWN # color of the text for the player's score
# The amount of space to the sides of the board to the edge of the window
# is used several times, so calculate it once here and store in variables.
XMARGIN = int((WINDOWWIDTH - GEMIMAGESIZE * BOARDWIDTH) / 2)
YMARGIN = int((WINDOWHEIGHT - GEMIMAGESIZE * BOARDHEIGHT) / 2)
# constants for direction values
UP = 'up'
DOWN = 'down'
LEFT = 'left'
RIGHT = 'right'
EMPTY_SPACE = -1 # an arbitrary, nonpositive value
ROWABOVEBOARD = 'row above board' # an arbitrary, noninteger value
def main():
global FPSCLOCK, DISPLAYSURF, GEMIMAGES, GAMESOUNDS, BASICFONT, BOARDRECTS
# Initial set up.
pygame.init()
FPSCLOCK = pygame.time.Clock()
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
pygame.display.set_caption('Gemgem')
BASICFONT = pygame.font.Font('freesansbold.ttf', 36)
# Load the images
GEMIMAGES = []
for i in range(1, NUMGEMIMAGES+1):
gemImage = pygame.image.load('gem%s.png' % i)
if gemImage.get_size() != (GEMIMAGESIZE, GEMIMAGESIZE):
gemImage = pygame.transform.smoothscale(gemImage, (GEMIMAGESIZE, GEMIMAGESIZE))
GEMIMAGES.append(gemImage)
# Load the sounds.
GAMESOUNDS = {}
GAMESOUNDS['bad swap'] = pygame.mixer.Sound('badswap.wav')
GAMESOUNDS['match'] = []
for i in range(NUMMATCHSOUNDS):
GAMESOUNDS['match'].append(pygame.mixer.Sound('match%s.wav' % i))
# Create pygame.Rect objects for each board space to
# do board-coordinate-to-pixel-coordinate conversions.
BOARDRECTS = []
for x in range(BOARDWIDTH):
BOARDRECTS.append([])
for y in range(BOARDHEIGHT):
r = pygame.Rect((XMARGIN + (x * GEMIMAGESIZE),
YMARGIN + (y * GEMIMAGESIZE),
GEMIMAGESIZE,
GEMIMAGESIZE))
BOARDRECTS[x].append(r)
while True:
runGame()
def runGame():
# Plays through a single game. When the game is over, this function returns.
# initialize the board
gameBoard = getBlankBoard()
score = 0
fillBoardAndAnimate(gameBoard, [], score) # Drop the initial gems.
# initialize variables for the start of a new game
firstSelectedGem = None
lastMouseDownX = None
lastMouseDownY = None
gameIsOver = False
lastScoreDeduction = time.time()
clickContinueTextSurf = None
while True: # main game loop
clickedSpace = None
for event in pygame.event.get(): # event handling loop
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
elif event.type == KEYUP and event.key == K_BACKSPACE:
return # start a new game
elif event.type == MOUSEBUTTONUP:
if gameIsOver:
return # after games ends, click to start a new game
if event.pos == (lastMouseDownX, lastMouseDownY):
# This event is a mouse click, not the end of a mouse drag.
clickedSpace = checkForGemClick(event.pos)
else:
# this is the end of a mouse drag
firstSelectedGem = checkForGemClick((lastMouseDownX, lastMouseDownY))
clickedSpace = checkForGemClick(event.pos)
if not firstSelectedGem or not clickedSpace:
# if not part of a valid drag, deselect both
firstSelectedGem = None
clickedSpace = None
elif event.type == MOUSEBUTTONDOWN:
# this is the start of a mouse click or mouse drag
lastMouseDownX, lastMouseDownY = event.pos
if clickedSpace and not firstSelectedGem:
# This was the first gem clicked on.
firstSelectedGem = clickedSpace
elif clickedSpace and firstSelectedGem:
# Two gems have been clicked on and selected. Swap the gems.
firstSwappingGem, secondSwappingGem = getSwappingGems(gameBoard, firstSelectedGem, clickedSpace)
if firstSwappingGem == None and secondSwappingGem == None:
# If both are None, then the gems were not adjacent
firstSelectedGem = None # deselect the first gem
continue
# Show the swap animation on the screen.
boardCopy = getBoardCopyMinusGems(gameBoard, (firstSwappingGem, secondSwappingGem))
animateMovingGems(boardCopy, [firstSwappingGem, secondSwappingGem], [], score)
# Swap the gems in the board data structure.
gameBoard[firstSwappingGem['x']][firstSwappingGem['y']] = secondSwappingGem['imageNum']
gameBoard[secondSwappingGem['x']][secondSwappingGem['y']] = firstSwappingGem['imageNum']
# See if this is a matching move.
matchedGems = findMatchingGems(gameBoard)
if matchedGems == []:
# Was not a matching move; swap the gems back
GAMESOUNDS['bad swap'].play()
animateMovingGems(boardCopy, [firstSwappingGem, secondSwappingGem], [], score)
gameBoard[firstSwappingGem['x']][firstSwappingGem['y']] = firstSwappingGem['imageNum']
gameBoard[secondSwappingGem['x']][secondSwappingGem['y']] = secondSwappingGem['imageNum']
else:
# This was a matching move.
scoreAdd = 0
while matchedGems != []:
# Remove matched gems, then pull down the board.
# points is a list of dicts that tells fillBoardAndAnimate()
# where on the screen to display text to show how many
# points the player got. points is a list because if
# the player gets multiple matches, then multiple points text should appear.
points = []
for gemSet in matchedGems:
scoreAdd += (10 + (len(gemSet) - 3) * 10)
for gem in gemSet:
gameBoard[gem[0]][gem[1]] = EMPTY_SPACE
points.append({'points': scoreAdd,
'x': gem[0] * GEMIMAGESIZE + XMARGIN,
'y': gem[1] * GEMIMAGESIZE + YMARGIN})
random.choice(GAMESOUNDS['match']).play()
score += scoreAdd
# Drop the new gems.
fillBoardAndAnimate(gameBoard, points, score)
# Check if there are any new matches.
matchedGems = findMatchingGems(gameBoard)
firstSelectedGem = None
if not canMakeMove(gameBoard):
gameIsOver = True
# Draw the board.
DISPLAYSURF.fill(BGCOLOR)
drawBoard(gameBoard)
if firstSelectedGem != None:
highlightSpace(firstSelectedGem['x'], firstSelectedGem['y'])
if gameIsOver:
if clickContinueTextSurf == None:
# Only render the text once. In future iterations, just
# use the Surface object already in clickContinueTextSurf
clickContinueTextSurf = BASICFONT.render('Final Score: %s (Click to continue)' % (score), 1, GAMEOVERCOLOR, GAMEOVERBGCOLOR)
clickContinueTextRect = clickContinueTextSurf.get_rect()
clickContinueTextRect.center = int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2)
DISPLAYSURF.blit(clickContinueTextSurf, clickContinueTextRect)
elif score > 0 and time.time() - lastScoreDeduction > DEDUCTSPEED:
# score drops over time
score -= 1
lastScoreDeduction = time.time()
drawScore(score)
pygame.display.update()
FPSCLOCK.tick(FPS)
def getSwappingGems(board, firstXY, secondXY):
# If the gems at the (X, Y) coordinates of the two gems are adjacent,
# then their 'direction' keys are set to the appropriate direction
# value to be swapped with each other.
# Otherwise, (None, None) is returned.
firstGem = {'imageNum': board[firstXY['x']][firstXY['y']],
'x': firstXY['x'],
'y': firstXY['y']}
secondGem = {'imageNum': board[secondXY['x']][secondXY['y']],
'x': secondXY['x'],
'y': secondXY['y']}
highlightedGem = None
if firstGem['x'] == secondGem['x'] + 1 and firstGem['y'] == secondGem['y']:
firstGem['direction'] = LEFT
secondGem['direction'] = RIGHT
elif firstGem['x'] == secondGem['x'] - 1 and firstGem['y'] == secondGem['y']:
firstGem['direction'] = RIGHT
secondGem['direction'] = LEFT
elif firstGem['y'] == secondGem['y'] + 1 and firstGem['x'] == secondGem['x']:
firstGem['direction'] = UP
secondGem['direction'] = DOWN
elif firstGem['y'] == secondGem['y'] - 1 and firstGem['x'] == secondGem['x']:
firstGem['direction'] = DOWN
secondGem['direction'] = UP
else:
# These gems are not adjacent and can't be swapped.
return None, None
return firstGem, secondGem
def getBlankBoard():
# Create and return a blank board data structure.
board = []
for x in range(BOARDWIDTH):
board.append([EMPTY_SPACE] * BOARDHEIGHT)
return board
def canMakeMove(board):
# Return True if the board is in a state where a matching
# move can be made on it. Otherwise return False.
# The patterns in oneOffPatterns represent gems that are configured
# in a way where it only takes one move to make a triplet.
oneOffPatterns = (((0,1), (1,0), (2,0)),
((0,1), (1,1), (2,0)),
((0,0), (1,1), (2,0)),
((0,1), (1,0), (2,1)),
((0,0), (1,0), (2,1)),
((0,0), (1,1), (2,1)),
((0,0), (0,2), (0,3)),
((0,0), (0,1), (0,3)))
# The x and y variables iterate over each space on the board.
# If we use + to represent the currently iterated space on the
# board, then this pattern: ((0,1), (1,0), (2,0))refers to identical
# gems being set up like this:
#
# +A
# B
# C
#
# That is, gem A is offset from the + by (0,1), gem B is offset
# by (1,0), and gem C is offset by (2,0). In this case, gem A can
# be swapped to the left to form a vertical three-in-a-row triplet.
#
# There are eight possible ways for the gems to be one move
# away from forming a triple, hence oneOffPattern has 8 patterns.
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
for pat in oneOffPatterns:
# check each possible pattern of "match in next move" to
# see if a possible move can be made.
if (getGemAt(board, x+pat[0][0], y+pat[0][1]) == \
getGemAt(board, x+pat[1][0], y+pat[1][1]) == \
getGemAt(board, x+pat[2][0], y+pat[2][1]) != None) or \
(getGemAt(board, x+pat[0][1], y+pat[0][0]) == \
getGemAt(board, x+pat[1][1], y+pat[1][0]) == \
getGemAt(board, x+pat[2][1], y+pat[2][0]) != None):
return True # return True the first time you find a pattern
return False
def drawMovingGem(gem, progress):
# Draw a gem sliding in the direction that its 'direction' key
# indicates. The progress parameter is a number from 0 (just
# starting) to 100 (slide complete).
movex = 0
movey = 0
progress *= 0.01
if gem['direction'] == UP:
movey = -int(progress * GEMIMAGESIZE)
elif gem['direction'] == DOWN:
movey = int(progress * GEMIMAGESIZE)
elif gem['direction'] == RIGHT:
movex = int(progress * GEMIMAGESIZE)
elif gem['direction'] == LEFT:
movex = -int(progress * GEMIMAGESIZE)
basex = gem['x']
basey = gem['y']
if basey == ROWABOVEBOARD:
basey = -1
pixelx = XMARGIN + (basex * GEMIMAGESIZE)
pixely = YMARGIN + (basey * GEMIMAGESIZE)
r = pygame.Rect( (pixelx + movex, pixely + movey, GEMIMAGESIZE, GEMIMAGESIZE) )
DISPLAYSURF.blit(GEMIMAGES[gem['imageNum']], r)
def pullDownAllGems(board):
# pulls down gems on the board to the bottom to fill in any gaps
for x in range(BOARDWIDTH):
gemsInColumn = []
for y in range(BOARDHEIGHT):
if board[x][y] != EMPTY_SPACE:
gemsInColumn.append(board[x][y])
board[x] = ([EMPTY_SPACE] * (BOARDHEIGHT - len(gemsInColumn))) + gemsInColumn
def getGemAt(board, x, y):
if x < 0 or y < 0 or x >= BOARDWIDTH or y >= BOARDHEIGHT:
return None
else:
return board[x][y]
def getDropSlots(board):
# Creates a "drop slot" for each column and fills the slot with a
# number of gems that that column is lacking. This function assumes
# that the gems have been gravity dropped already.
boardCopy = copy.deepcopy(board)
pullDownAllGems(boardCopy)
dropSlots = []
for i in range(BOARDWIDTH):
dropSlots.append([])
# count the number of empty spaces in each column on the board
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT-1, -1, -1): # start from bottom, going up
if boardCopy[x][y] == EMPTY_SPACE:
possibleGems = list(range(len(GEMIMAGES)))
for offsetX, offsetY in ((0, -1), (1, 0), (0, 1), (-1, 0)):
# Narrow down the possible gems we should put in the
# blank space so we don't end up putting an two of
# the same gems next to each other when they drop.
neighborGem = getGemAt(boardCopy, x + offsetX, y + offsetY)
if neighborGem != None and neighborGem in possibleGems:
possibleGems.remove(neighborGem)
newGem = random.choice(possibleGems)
boardCopy[x][y] = newGem
dropSlots[x].append(newGem)
return dropSlots
def findMatchingGems(board):
gemsToRemove = [] # a list of lists of gems in matching triplets that should be removed
boardCopy = copy.deepcopy(board)
# loop through each space, checking for 3 adjacent identical gems
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
# look for horizontal matches
if getGemAt(boardCopy, x, y) == getGemAt(boardCopy, x + 1, y) == getGemAt(boardCopy, x + 2, y) and getGemAt(boardCopy, x, y) != EMPTY_SPACE:
targetGem = boardCopy[x][y]
offset = 0
removeSet = []
while getGemAt(boardCopy, x + offset, y) == targetGem:
# keep checking, in case there's more than 3 gems in a row
removeSet.append((x + offset, y))
boardCopy[x + offset][y] = EMPTY_SPACE
offset += 1
gemsToRemove.append(removeSet)
# look for vertical matches
if getGemAt(boardCopy, x, y) == getGemAt(boardCopy, x, y + 1) == getGemAt(boardCopy, x, y + 2) and getGemAt(boardCopy, x, y) != EMPTY_SPACE:
targetGem = boardCopy[x][y]
offset = 0
removeSet = []
while getGemAt(boardCopy, x, y + offset) == targetGem:
# keep checking if there's more than 3 gems in a row
removeSet.append((x, y + offset))
boardCopy[x][y + offset] = EMPTY_SPACE
offset += 1
gemsToRemove.append(removeSet)
return gemsToRemove
def highlightSpace(x, y):
pygame.draw.rect(DISPLAYSURF, HIGHLIGHTCOLOR, BOARDRECTS[x][y], 4)
def getDroppingGems(board):
# Find all the gems that have an empty space below them
boardCopy = copy.deepcopy(board)
droppingGems = []
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT - 2, -1, -1):
if boardCopy[x][y + 1] == EMPTY_SPACE and boardCopy[x][y] != EMPTY_SPACE:
# This space drops if not empty but the space below it is
droppingGems.append( {'imageNum': boardCopy[x][y], 'x': x, 'y': y, 'direction': DOWN} )
boardCopy[x][y] = EMPTY_SPACE
return droppingGems
def animateMovingGems(board, gems, pointsText, score):
# pointsText is a dictionary with keys 'x', 'y', and 'points'
progress = 0 # progress at 0 represents beginning, 100 means finished.
while progress < 100: # animation loop
DISPLAYSURF.fill(BGCOLOR)
drawBoard(board)
for gem in gems: # Draw each gem.
drawMovingGem(gem, progress)
drawScore(score)
for pointText in pointsText:
pointsSurf = BASICFONT.render(str(pointText['points']), 1, SCORECOLOR)
pointsRect = pointsSurf.get_rect()
pointsRect.center = (pointText['x'], pointText['y'])
DISPLAYSURF.blit(pointsSurf, pointsRect)
pygame.display.update()
FPSCLOCK.tick(FPS)
progress += MOVERATE # progress the animation a little bit more for the next frame
def moveGems(board, movingGems):
# movingGems is a list of dicts with keys x, y, direction, imageNum
for gem in movingGems:
if gem['y'] != ROWABOVEBOARD:
board[gem['x']][gem['y']] = EMPTY_SPACE
movex = 0
movey = 0
if gem['direction'] == LEFT:
movex = -1
elif gem['direction'] == RIGHT:
movex = 1
elif gem['direction'] == DOWN:
movey = 1
elif gem['direction'] == UP:
movey = -1
board[gem['x'] + movex][gem['y'] + movey] = gem['imageNum']
else:
# gem is located above the board (where new gems come from)
board[gem['x']][0] = gem['imageNum'] # move to top row
def fillBoardAndAnimate(board, points, score):
dropSlots = getDropSlots(board)
while dropSlots != [[]] * BOARDWIDTH:
# do the dropping animation as long as there are more gems to drop
movingGems = getDroppingGems(board)
for x in range(len(dropSlots)):
if len(dropSlots[x]) != 0:
# cause the lowest gem in each slot to begin moving in the DOWN direction
movingGems.append({'imageNum': dropSlots[x][0], 'x': x, 'y': ROWABOVEBOARD, 'direction': DOWN})
boardCopy = getBoardCopyMinusGems(board, movingGems)
animateMovingGems(boardCopy, movingGems, points, score)
moveGems(board, movingGems)
# Make the next row of gems from the drop slots
# the lowest by deleting the previous lowest gems.
for x in range(len(dropSlots)):
if len(dropSlots[x]) == 0:
continue
board[x][0] = dropSlots[x][0]
del dropSlots[x][0]
def checkForGemClick(pos):
# See if the mouse click was on the board
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
if BOARDRECTS[x][y].collidepoint(pos[0], pos[1]):
return {'x': x, 'y': y}
return None # Click was not on the board.
def drawBoard(board):
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
pygame.draw.rect(DISPLAYSURF, GRIDCOLOR, BOARDRECTS[x][y], 1)
gemToDraw = board[x][y]
if gemToDraw != EMPTY_SPACE:
DISPLAYSURF.blit(GEMIMAGES[gemToDraw], BOARDRECTS[x][y])
def getBoardCopyMinusGems(board, gems):
# Creates and returns a copy of the passed board data structure,
# with the gems in the "gems" list removed from it.
#
# Gems is a list of dicts, with keys x, y, direction, imageNum
boardCopy = copy.deepcopy(board)
# Remove some of the gems from this board data structure copy.
for gem in gems:
if gem['y'] != ROWABOVEBOARD:
boardCopy[gem['x']][gem['y']] = EMPTY_SPACE
return boardCopy
def drawScore(score):
scoreImg = BASICFONT.render(str(score), 1, SCORECOLOR)
scoreRect = scoreImg.get_rect()
scoreRect.bottomleft = (10, WINDOWHEIGHT - 6)
DISPLAYSURF.blit(scoreImg, scoreRect)
if __name__ == '__main__':
main()
요약
이 게임 프로그램을 통해 어떤 게임을 만들고 싶은지, 어떻게 작성할지 자신만의 아이디어를 얻으셨기를 바랍니다. 자신만의 아이디어가 없더라도 자신이 플레이한 다른 게임의 복제품을 만들어 보는 것도 좋은 연습이 될 것입니다.
다음은 파이썬 프로그래밍에 대해 자세히 알아볼 수 있는 웹사이트입니다:
- 공식 파이게임 웹사이트에는 파이게임 라이브러리를 활용하는 수백 개의 게임에 대한 소스 코드가 있습니다. 다른 사람의 소스 코드를 다운로드하여 읽으면 많은 것을 배울 수 있습니다.
python.org/doc/ - 모든 Python 모듈 및 함수에 대한 더 많은 Python 튜토리얼과 문서.
pygame.org/docs/ - Pygame 모듈 및 기능에 대한 전체 문서
프로그래밍 학습을 위한 리소스를 찾는 데 도움을 줄 수 있는 많은 사용자가 있습니다.
_ - 이 책의 웹사이트로, 이 프로그램의 모든 소스 코드와 추가 정보가 포함되어 있습니다. 이 사이트에는 파이게임 프로그램에 사용된 이미지와 사운드 파일도 포함되어 있습니다.
_ - 기본 Python 프로그래밍을 다루는 "Invent 나만의 컴퓨터 게임 만들기" 책 웹사이트.
invpy.com/wiki - 특정 내용을 알아야 할 경우 참조할 수 있는 개별 Python 프로그래밍 개념을 다루는 위키입니다.
invpy.com/traces - 이 책의 프로그램 실행을 단계별로 따라할 수 있도록 도와주는 웹 애플리케이션입니다.
invpy.com/videos - 이 책에 수록된 절차에 대한 동영상입니다.
gamedevlessons.com - 비디오 게임을 디자인하고 프로그래밍하는 방법에 대한 유용한 사이트입니다.
또는 월드 와이드 웹을 검색하여 Python에 대해 자세히 알아볼 수 있습니다. 검색 사이트 google.com으로 이동하여 "Python 프로그래밍" 또는 "Python 튜토리얼"을 검색하면 Python 프로그래밍에 관한 더 많은 사이트를 찾을 수 있습니다.
이제 나만의 게임을 만들어 보세요. 행운을 빕니다!





