├── gemgem ├── gem1.png ├── gem2.png ├── gem3.png ├── gem4.png ├── gem5.png ├── gem6.png ├── gem7.png ├── badswap.wav ├── match0.wav ├── match1.wav ├── match2.wav ├── match3.wav ├── match4.wav ├── match5.wav └── gemgem.py ├── simulate ├── beep1.ogg ├── beep2.ogg ├── beep3.ogg ├── beep4.ogg └── simulate.py ├── starpusher ├── boy.png ├── Rock.png ├── Star.png ├── Selector.png ├── catgirl.png ├── horngirl.png ├── pinkgirl.png ├── princess.png ├── Grass_Block.png ├── Plain_Block.png ├── RedSelector.png ├── Tree_Short.png ├── Tree_Tall.png ├── Tree_Ugly.png ├── Wall_Block.png ├── Wood_Block.png ├── star_solved.png ├── star_title.png ├── Wall_Block_Tall.png ├── Wood_Block_Tall.png └── starpusher.py ├── catanimation ├── cat.png ├── catblink.png └── catanimation.py ├── squirrel ├── gameicon.png ├── grass1.png ├── grass2.png ├── grass3.png ├── grass4.png ├── squirrel.png └── squirrel.py ├── tetromino ├── tetrisb.mid ├── tetrisc.mid └── tetromino.py ├── flippy ├── flippyboard.png ├── flippybackground.png └── flippy.py ├── fourinarow ├── 4row_red.png ├── 4row_tie.png ├── 4row_arrow.png ├── 4row_black.png ├── 4row_board.png ├── 4row_humanwinner.png ├── 4row_computerwinner.png └── fourinarow.py ├── inkspill ├── inkspilllogo.png ├── inkspillspot.png ├── inkspillsettings.png ├── inkspillresetbutton.png ├── inkspillsettingsbutton.png └── inkspill.py ├── README.md ├── blankpygame └── blankpygame.py ├── memorypuzzle_obfuscated └── memorypuzzle_obfuscated.py ├── wormy └── wormy.py ├── memorypuzzle └── memorypuzzle.py └── slidepuzzle └── slidepuzzle.py /gemgem/gem1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/gemgem/gem1.png -------------------------------------------------------------------------------- /gemgem/gem2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/gemgem/gem2.png -------------------------------------------------------------------------------- /gemgem/gem3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/gemgem/gem3.png -------------------------------------------------------------------------------- /gemgem/gem4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/gemgem/gem4.png -------------------------------------------------------------------------------- /gemgem/gem5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/gemgem/gem5.png -------------------------------------------------------------------------------- /gemgem/gem6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/gemgem/gem6.png -------------------------------------------------------------------------------- /gemgem/gem7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/gemgem/gem7.png -------------------------------------------------------------------------------- /gemgem/badswap.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/gemgem/badswap.wav -------------------------------------------------------------------------------- /gemgem/match0.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/gemgem/match0.wav -------------------------------------------------------------------------------- /gemgem/match1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/gemgem/match1.wav -------------------------------------------------------------------------------- /gemgem/match2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/gemgem/match2.wav -------------------------------------------------------------------------------- /gemgem/match3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/gemgem/match3.wav -------------------------------------------------------------------------------- /gemgem/match4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/gemgem/match4.wav -------------------------------------------------------------------------------- /gemgem/match5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/gemgem/match5.wav -------------------------------------------------------------------------------- /simulate/beep1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/simulate/beep1.ogg -------------------------------------------------------------------------------- /simulate/beep2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/simulate/beep2.ogg -------------------------------------------------------------------------------- /simulate/beep3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/simulate/beep3.ogg -------------------------------------------------------------------------------- /simulate/beep4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/simulate/beep4.ogg -------------------------------------------------------------------------------- /starpusher/boy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/boy.png -------------------------------------------------------------------------------- /catanimation/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/catanimation/cat.png -------------------------------------------------------------------------------- /squirrel/gameicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/squirrel/gameicon.png -------------------------------------------------------------------------------- /squirrel/grass1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/squirrel/grass1.png -------------------------------------------------------------------------------- /squirrel/grass2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/squirrel/grass2.png -------------------------------------------------------------------------------- /squirrel/grass3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/squirrel/grass3.png -------------------------------------------------------------------------------- /squirrel/grass4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/squirrel/grass4.png -------------------------------------------------------------------------------- /squirrel/squirrel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/squirrel/squirrel.png -------------------------------------------------------------------------------- /starpusher/Rock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/Rock.png -------------------------------------------------------------------------------- /starpusher/Star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/Star.png -------------------------------------------------------------------------------- /tetromino/tetrisb.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/tetromino/tetrisb.mid -------------------------------------------------------------------------------- /tetromino/tetrisc.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/tetromino/tetrisc.mid -------------------------------------------------------------------------------- /flippy/flippyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/flippy/flippyboard.png -------------------------------------------------------------------------------- /fourinarow/4row_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/fourinarow/4row_red.png -------------------------------------------------------------------------------- /fourinarow/4row_tie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/fourinarow/4row_tie.png -------------------------------------------------------------------------------- /starpusher/Selector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/Selector.png -------------------------------------------------------------------------------- /starpusher/catgirl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/catgirl.png -------------------------------------------------------------------------------- /starpusher/horngirl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/horngirl.png -------------------------------------------------------------------------------- /starpusher/pinkgirl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/pinkgirl.png -------------------------------------------------------------------------------- /starpusher/princess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/princess.png -------------------------------------------------------------------------------- /catanimation/catblink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/catanimation/catblink.png -------------------------------------------------------------------------------- /fourinarow/4row_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/fourinarow/4row_arrow.png -------------------------------------------------------------------------------- /fourinarow/4row_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/fourinarow/4row_black.png -------------------------------------------------------------------------------- /fourinarow/4row_board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/fourinarow/4row_board.png -------------------------------------------------------------------------------- /inkspill/inkspilllogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/inkspill/inkspilllogo.png -------------------------------------------------------------------------------- /inkspill/inkspillspot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/inkspill/inkspillspot.png -------------------------------------------------------------------------------- /starpusher/Grass_Block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/Grass_Block.png -------------------------------------------------------------------------------- /starpusher/Plain_Block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/Plain_Block.png -------------------------------------------------------------------------------- /starpusher/RedSelector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/RedSelector.png -------------------------------------------------------------------------------- /starpusher/Tree_Short.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/Tree_Short.png -------------------------------------------------------------------------------- /starpusher/Tree_Tall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/Tree_Tall.png -------------------------------------------------------------------------------- /starpusher/Tree_Ugly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/Tree_Ugly.png -------------------------------------------------------------------------------- /starpusher/Wall_Block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/Wall_Block.png -------------------------------------------------------------------------------- /starpusher/Wood_Block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/Wood_Block.png -------------------------------------------------------------------------------- /starpusher/star_solved.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/star_solved.png -------------------------------------------------------------------------------- /starpusher/star_title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/star_title.png -------------------------------------------------------------------------------- /flippy/flippybackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/flippy/flippybackground.png -------------------------------------------------------------------------------- /fourinarow/4row_humanwinner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/fourinarow/4row_humanwinner.png -------------------------------------------------------------------------------- /inkspill/inkspillsettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/inkspill/inkspillsettings.png -------------------------------------------------------------------------------- /starpusher/Wall_Block_Tall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/Wall_Block_Tall.png -------------------------------------------------------------------------------- /starpusher/Wood_Block_Tall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/starpusher/Wood_Block_Tall.png -------------------------------------------------------------------------------- /inkspill/inkspillresetbutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/inkspill/inkspillresetbutton.png -------------------------------------------------------------------------------- /fourinarow/4row_computerwinner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/fourinarow/4row_computerwinner.png -------------------------------------------------------------------------------- /inkspill/inkspillsettingsbutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/making-games-with-python-and-pygame/HEAD/inkspill/inkspillsettingsbutton.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | making-games-with-python-and-pygame 2 | =================================== 3 | 4 | The source code for the game programs for the book, "Making Games with Python & Pygame" 5 | -------------------------------------------------------------------------------- /blankpygame/blankpygame.py: -------------------------------------------------------------------------------- 1 | import pygame, sys 2 | from pygame.locals import * 3 | 4 | pygame.init() 5 | DISPLAYSURF = pygame.display.set_mode((400, 300)) 6 | pygame.display.set_caption('Hello World!') 7 | while True: # main game loop 8 | for event in pygame.event.get(): 9 | if event.type == QUIT: 10 | pygame.quit() 11 | sys.exit() 12 | pygame.display.update() -------------------------------------------------------------------------------- /catanimation/catanimation.py: -------------------------------------------------------------------------------- 1 | import pygame, sys 2 | from pygame.locals import * 3 | 4 | pygame.init() 5 | 6 | FPS = 30 # frames per second setting 7 | fpsClock = pygame.time.Clock() 8 | 9 | # set up the window 10 | DISPLAYSURF = pygame.display.set_mode((400, 300), 0, 32) 11 | pygame.display.set_caption('Animation') 12 | 13 | WHITE = (255, 255, 255) 14 | catImg = pygame.image.load('cat.png') 15 | catx = 10 16 | caty = 10 17 | direction = 'right' 18 | 19 | while True: # the main game loop 20 | DISPLAYSURF.fill(WHITE) 21 | 22 | if direction == 'right': 23 | catx += 5 24 | if catx == 280: 25 | direction = 'down' 26 | elif direction == 'down': 27 | caty += 5 28 | if caty == 220: 29 | direction = 'left' 30 | elif direction == 'left': 31 | catx -= 5 32 | if catx == 10: 33 | direction = 'up' 34 | elif direction == 'up': 35 | caty -= 5 36 | if caty == 10: 37 | direction = 'right' 38 | 39 | DISPLAYSURF.blit(catImg, (catx, caty)) 40 | 41 | for event in pygame.event.get(): 42 | if event.type == QUIT: 43 | pygame.quit() 44 | sys.exit() 45 | 46 | pygame.display.update() 47 | fpsClock.tick(FPS) -------------------------------------------------------------------------------- /memorypuzzle_obfuscated/memorypuzzle_obfuscated.py: -------------------------------------------------------------------------------- 1 | import random, pygame, sys 2 | from pygame.locals import * 3 | def hhh(): 4 | global a, b 5 | pygame.init() 6 | a = pygame.time.Clock() 7 | b = pygame.display.set_mode((640, 480)) 8 | j = 0 9 | k = 0 10 | pygame.display.set_caption('Memory Game') 11 | i = c() 12 | hh = d(False) 13 | h = None 14 | b.fill((60, 60, 100)) 15 | g(i) 16 | while True: 17 | e = False 18 | b.fill((60, 60, 100)) 19 | f(i, hh) 20 | for eee in pygame.event.get(): 21 | if eee.type == QUIT or (eee.type == KEYUP and eee.key == K_ESCAPE): 22 | pygame.quit() 23 | sys.exit() 24 | elif eee.type == MOUSEMOTION: 25 | j, k = eee.pos 26 | elif eee.type == MOUSEBUTTONUP: 27 | j, k = eee.pos 28 | e = True 29 | bb, ee = m(j, k) 30 | if bb != None and ee != None: 31 | if not hh[bb][ee]: 32 | n(bb, ee) 33 | if not hh[bb][ee] and e: 34 | o(i, [(bb, ee)]) 35 | hh[bb][ee] = True 36 | if h == None: 37 | h = (bb, ee) 38 | else: 39 | q, fff = s(i, h[0], h[1]) 40 | r, ggg = s(i, bb, ee) 41 | if q != r or fff != ggg: 42 | pygame.time.wait(1000) 43 | p(i, [(h[0], h[1]), (bb, ee)]) 44 | hh[h[0]][h[1]] = False 45 | hh[bb][ee] = False 46 | elif ii(hh): 47 | jj(i) 48 | pygame.time.wait(2000) 49 | i = c() 50 | hh = d(False) 51 | f(i, hh) 52 | pygame.display.update() 53 | pygame.time.wait(1000) 54 | g(i) 55 | h = None 56 | pygame.display.update() 57 | a.tick(30) 58 | def d(ccc): 59 | hh = [] 60 | for i in range(10): 61 | hh.append([ccc] * 7) 62 | return hh 63 | def c(): 64 | rr = [] 65 | for tt in ((255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 128, 0), (255, 0, 255), (0, 255, 255)): 66 | for ss in ('a', 'b', 'c', 'd', 'e'): 67 | rr.append( (ss, tt) ) 68 | random.shuffle(rr) 69 | rr = rr[:35] * 2 70 | random.shuffle(rr) 71 | bbb = [] 72 | for x in range(10): 73 | v = [] 74 | for y in range(7): 75 | v.append(rr[0]) 76 | del rr[0] 77 | bbb.append(v) 78 | return bbb 79 | def t(vv, uu): 80 | ww = [] 81 | for i in range(0, len(uu), vv): 82 | ww.append(uu[i:i + vv]) 83 | return ww 84 | def aa(bb, ee): 85 | return (bb * 50 + 70, ee * 50 + 65) 86 | def m(x, y): 87 | for bb in range(10): 88 | for ee in range(7): 89 | oo, ddd = aa(bb, ee) 90 | aaa = pygame.Rect(oo, ddd, 40, 40) 91 | if aaa.collidepoint(x, y): 92 | return (bb, ee) 93 | return (None, None) 94 | def w(ss, tt, bb, ee): 95 | oo, ddd = aa(bb, ee) 96 | if ss == 'a': 97 | pygame.draw.circle(b, tt, (oo + 20, ddd + 20), 15) 98 | pygame.draw.circle(b, (60, 60, 100), (oo + 20, ddd + 20), 5) 99 | elif ss == 'b': 100 | pygame.draw.rect(b, tt, (oo + 10, ddd + 10, 20, 20)) 101 | elif ss == 'c': 102 | pygame.draw.polygon(b, tt, ((oo + 20, ddd), (oo + 40 - 1, ddd + 20), (oo + 20, ddd + 40 - 1), (oo, ddd + 20))) 103 | elif ss == 'd': 104 | for i in range(0, 40, 4): 105 | pygame.draw.line(b, tt, (oo, ddd + i), (oo + i, ddd)) 106 | pygame.draw.line(b, tt, (oo + i, ddd + 39), (oo + 39, ddd + i)) 107 | elif ss == 'e': 108 | pygame.draw.ellipse(b, tt, (oo, ddd + 10, 40, 20)) 109 | def s(bbb, bb, ee): 110 | return bbb[bb][ee][0], bbb[bb][ee][1] 111 | def dd(bbb, boxes, gg): 112 | for box in boxes: 113 | oo, ddd = aa(box[0], box[1]) 114 | pygame.draw.rect(b, (60, 60, 100), (oo, ddd, 40, 40)) 115 | ss, tt = s(bbb, box[0], box[1]) 116 | w(ss, tt, box[0], box[1]) 117 | if gg > 0: 118 | pygame.draw.rect(b, (255, 255, 255), (oo, ddd, gg, 40)) 119 | pygame.display.update() 120 | a.tick(30) 121 | def o(bbb, cc): 122 | for gg in range(40, (-8) - 1, -8): 123 | dd(bbb, cc, gg) 124 | def p(bbb, ff): 125 | for gg in range(0, 48, 8): 126 | dd(bbb, ff, gg) 127 | def f(bbb, pp): 128 | for bb in range(10): 129 | for ee in range(7): 130 | oo, ddd = aa(bb, ee) 131 | if not pp[bb][ee]: 132 | pygame.draw.rect(b, (255, 255, 255), (oo, ddd, 40, 40)) 133 | else: 134 | ss, tt = s(bbb, bb, ee) 135 | w(ss, tt, bb, ee) 136 | def n(bb, ee): 137 | oo, ddd = aa(bb, ee) 138 | pygame.draw.rect(b, (0, 0, 255), (oo - 5, ddd - 5, 50, 50), 4) 139 | def g(bbb): 140 | mm = d(False) 141 | boxes = [] 142 | for x in range(10): 143 | for y in range(7): 144 | boxes.append( (x, y) ) 145 | random.shuffle(boxes) 146 | kk = t(8, boxes) 147 | f(bbb, mm) 148 | for nn in kk: 149 | o(bbb, nn) 150 | p(bbb, nn) 151 | def jj(bbb): 152 | mm = d(True) 153 | tt1 = (100, 100, 100) 154 | tt2 = (60, 60, 100) 155 | for i in range(13): 156 | tt1, tt2 = tt2, tt1 157 | b.fill(tt1) 158 | f(bbb, mm) 159 | pygame.display.update() 160 | pygame.time.wait(300) 161 | def ii(hh): 162 | for i in hh: 163 | if False in i: 164 | return False 165 | return True 166 | if __name__ == '__main__': 167 | hhh() -------------------------------------------------------------------------------- /wormy/wormy.py: -------------------------------------------------------------------------------- 1 | # Wormy (a Nibbles clone) 2 | # By Al Sweigart al@inventwithpython.com 3 | # http://inventwithpython.com/pygame 4 | # Released under a "Simplified BSD" license 5 | 6 | import random, pygame, sys 7 | from pygame.locals import * 8 | 9 | FPS = 15 10 | WINDOWWIDTH = 640 11 | WINDOWHEIGHT = 480 12 | CELLSIZE = 20 13 | assert WINDOWWIDTH % CELLSIZE == 0, "Window width must be a multiple of cell size." 14 | assert WINDOWHEIGHT % CELLSIZE == 0, "Window height must be a multiple of cell size." 15 | CELLWIDTH = int(WINDOWWIDTH / CELLSIZE) 16 | CELLHEIGHT = int(WINDOWHEIGHT / CELLSIZE) 17 | 18 | # R G B 19 | WHITE = (255, 255, 255) 20 | BLACK = ( 0, 0, 0) 21 | RED = (255, 0, 0) 22 | GREEN = ( 0, 255, 0) 23 | DARKGREEN = ( 0, 155, 0) 24 | DARKGRAY = ( 40, 40, 40) 25 | BGCOLOR = BLACK 26 | 27 | UP = 'up' 28 | DOWN = 'down' 29 | LEFT = 'left' 30 | RIGHT = 'right' 31 | 32 | HEAD = 0 # syntactic sugar: index of the worm's head 33 | 34 | def main(): 35 | global FPSCLOCK, DISPLAYSURF, BASICFONT 36 | 37 | pygame.init() 38 | FPSCLOCK = pygame.time.Clock() 39 | DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) 40 | BASICFONT = pygame.font.Font('freesansbold.ttf', 18) 41 | pygame.display.set_caption('Wormy') 42 | 43 | showStartScreen() 44 | while True: 45 | runGame() 46 | showGameOverScreen() 47 | 48 | 49 | def runGame(): 50 | # Set a random start point. 51 | startx = random.randint(5, CELLWIDTH - 6) 52 | starty = random.randint(5, CELLHEIGHT - 6) 53 | wormCoords = [{'x': startx, 'y': starty}, 54 | {'x': startx - 1, 'y': starty}, 55 | {'x': startx - 2, 'y': starty}] 56 | direction = RIGHT 57 | 58 | # Start the apple in a random place. 59 | apple = getRandomLocation() 60 | 61 | while True: # main game loop 62 | for event in pygame.event.get(): # event handling loop 63 | if event.type == QUIT: 64 | terminate() 65 | elif event.type == KEYDOWN: 66 | if (event.key == K_LEFT or event.key == K_a) and direction != RIGHT: 67 | direction = LEFT 68 | elif (event.key == K_RIGHT or event.key == K_d) and direction != LEFT: 69 | direction = RIGHT 70 | elif (event.key == K_UP or event.key == K_w) and direction != DOWN: 71 | direction = UP 72 | elif (event.key == K_DOWN or event.key == K_s) and direction != UP: 73 | direction = DOWN 74 | elif event.key == K_ESCAPE: 75 | terminate() 76 | 77 | # check if the worm has hit itself or the edge 78 | if wormCoords[HEAD]['x'] == -1 or wormCoords[HEAD]['x'] == CELLWIDTH or wormCoords[HEAD]['y'] == -1 or wormCoords[HEAD]['y'] == CELLHEIGHT: 79 | return # game over 80 | for wormBody in wormCoords[1:]: 81 | if wormBody['x'] == wormCoords[HEAD]['x'] and wormBody['y'] == wormCoords[HEAD]['y']: 82 | return # game over 83 | 84 | # check if worm has eaten an apple 85 | if wormCoords[HEAD]['x'] == apple['x'] and wormCoords[HEAD]['y'] == apple['y']: 86 | # don't remove worm's tail segment 87 | apple = getRandomLocation() # set a new apple somewhere 88 | else: 89 | del wormCoords[-1] # remove worm's tail segment 90 | 91 | # move the worm by adding a segment in the direction it is moving 92 | if direction == UP: 93 | newHead = {'x': wormCoords[HEAD]['x'], 'y': wormCoords[HEAD]['y'] - 1} 94 | elif direction == DOWN: 95 | newHead = {'x': wormCoords[HEAD]['x'], 'y': wormCoords[HEAD]['y'] + 1} 96 | elif direction == LEFT: 97 | newHead = {'x': wormCoords[HEAD]['x'] - 1, 'y': wormCoords[HEAD]['y']} 98 | elif direction == RIGHT: 99 | newHead = {'x': wormCoords[HEAD]['x'] + 1, 'y': wormCoords[HEAD]['y']} 100 | wormCoords.insert(0, newHead) 101 | DISPLAYSURF.fill(BGCOLOR) 102 | drawGrid() 103 | drawWorm(wormCoords) 104 | drawApple(apple) 105 | drawScore(len(wormCoords) - 3) 106 | pygame.display.update() 107 | FPSCLOCK.tick(FPS) 108 | 109 | def drawPressKeyMsg(): 110 | pressKeySurf = BASICFONT.render('Press a key to play.', True, DARKGRAY) 111 | pressKeyRect = pressKeySurf.get_rect() 112 | pressKeyRect.topleft = (WINDOWWIDTH - 200, WINDOWHEIGHT - 30) 113 | DISPLAYSURF.blit(pressKeySurf, pressKeyRect) 114 | 115 | 116 | def checkForKeyPress(): 117 | if len(pygame.event.get(QUIT)) > 0: 118 | terminate() 119 | 120 | keyUpEvents = pygame.event.get(KEYUP) 121 | if len(keyUpEvents) == 0: 122 | return None 123 | if keyUpEvents[0].key == K_ESCAPE: 124 | terminate() 125 | return keyUpEvents[0].key 126 | 127 | 128 | def showStartScreen(): 129 | titleFont = pygame.font.Font('freesansbold.ttf', 100) 130 | titleSurf1 = titleFont.render('Wormy!', True, WHITE, DARKGREEN) 131 | titleSurf2 = titleFont.render('Wormy!', True, GREEN) 132 | 133 | degrees1 = 0 134 | degrees2 = 0 135 | while True: 136 | DISPLAYSURF.fill(BGCOLOR) 137 | rotatedSurf1 = pygame.transform.rotate(titleSurf1, degrees1) 138 | rotatedRect1 = rotatedSurf1.get_rect() 139 | rotatedRect1.center = (WINDOWWIDTH / 2, WINDOWHEIGHT / 2) 140 | DISPLAYSURF.blit(rotatedSurf1, rotatedRect1) 141 | 142 | rotatedSurf2 = pygame.transform.rotate(titleSurf2, degrees2) 143 | rotatedRect2 = rotatedSurf2.get_rect() 144 | rotatedRect2.center = (WINDOWWIDTH / 2, WINDOWHEIGHT / 2) 145 | DISPLAYSURF.blit(rotatedSurf2, rotatedRect2) 146 | 147 | drawPressKeyMsg() 148 | 149 | if checkForKeyPress(): 150 | pygame.event.get() # clear event queue 151 | return 152 | pygame.display.update() 153 | FPSCLOCK.tick(FPS) 154 | degrees1 += 3 # rotate by 3 degrees each frame 155 | degrees2 += 7 # rotate by 7 degrees each frame 156 | 157 | 158 | def terminate(): 159 | pygame.quit() 160 | sys.exit() 161 | 162 | 163 | def getRandomLocation(): 164 | return {'x': random.randint(0, CELLWIDTH - 1), 'y': random.randint(0, CELLHEIGHT - 1)} 165 | 166 | 167 | def showGameOverScreen(): 168 | gameOverFont = pygame.font.Font('freesansbold.ttf', 150) 169 | gameSurf = gameOverFont.render('Game', True, WHITE) 170 | overSurf = gameOverFont.render('Over', True, WHITE) 171 | gameRect = gameSurf.get_rect() 172 | overRect = overSurf.get_rect() 173 | gameRect.midtop = (WINDOWWIDTH / 2, 10) 174 | overRect.midtop = (WINDOWWIDTH / 2, gameRect.height + 10 + 25) 175 | 176 | DISPLAYSURF.blit(gameSurf, gameRect) 177 | DISPLAYSURF.blit(overSurf, overRect) 178 | drawPressKeyMsg() 179 | pygame.display.update() 180 | pygame.time.wait(500) 181 | checkForKeyPress() # clear out any key presses in the event queue 182 | 183 | while True: 184 | if checkForKeyPress(): 185 | pygame.event.get() # clear event queue 186 | return 187 | 188 | def drawScore(score): 189 | scoreSurf = BASICFONT.render('Score: %s' % (score), True, WHITE) 190 | scoreRect = scoreSurf.get_rect() 191 | scoreRect.topleft = (WINDOWWIDTH - 120, 10) 192 | DISPLAYSURF.blit(scoreSurf, scoreRect) 193 | 194 | 195 | def drawWorm(wormCoords): 196 | for coord in wormCoords: 197 | x = coord['x'] * CELLSIZE 198 | y = coord['y'] * CELLSIZE 199 | wormSegmentRect = pygame.Rect(x, y, CELLSIZE, CELLSIZE) 200 | pygame.draw.rect(DISPLAYSURF, DARKGREEN, wormSegmentRect) 201 | wormInnerSegmentRect = pygame.Rect(x + 4, y + 4, CELLSIZE - 8, CELLSIZE - 8) 202 | pygame.draw.rect(DISPLAYSURF, GREEN, wormInnerSegmentRect) 203 | 204 | 205 | def drawApple(coord): 206 | x = coord['x'] * CELLSIZE 207 | y = coord['y'] * CELLSIZE 208 | appleRect = pygame.Rect(x, y, CELLSIZE, CELLSIZE) 209 | pygame.draw.rect(DISPLAYSURF, RED, appleRect) 210 | 211 | 212 | def drawGrid(): 213 | for x in range(0, WINDOWWIDTH, CELLSIZE): # draw vertical lines 214 | pygame.draw.line(DISPLAYSURF, DARKGRAY, (x, 0), (x, WINDOWHEIGHT)) 215 | for y in range(0, WINDOWHEIGHT, CELLSIZE): # draw horizontal lines 216 | pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, y), (WINDOWWIDTH, y)) 217 | 218 | 219 | if __name__ == '__main__': 220 | main() -------------------------------------------------------------------------------- /simulate/simulate.py: -------------------------------------------------------------------------------- 1 | # Simulate (a Simon clone) 2 | # By Al Sweigart al@inventwithpython.com 3 | # http://inventwithpython.com/pygame 4 | # Released under a "Simplified BSD" license 5 | 6 | import random, sys, time, pygame 7 | from pygame.locals import * 8 | 9 | FPS = 30 10 | WINDOWWIDTH = 640 11 | WINDOWHEIGHT = 480 12 | FLASHSPEED = 500 # in milliseconds 13 | FLASHDELAY = 200 # in milliseconds 14 | BUTTONSIZE = 200 15 | BUTTONGAPSIZE = 20 16 | TIMEOUT = 4 # seconds before game over if no button is pushed. 17 | 18 | # R G B 19 | WHITE = (255, 255, 255) 20 | BLACK = ( 0, 0, 0) 21 | BRIGHTRED = (255, 0, 0) 22 | RED = (155, 0, 0) 23 | BRIGHTGREEN = ( 0, 255, 0) 24 | GREEN = ( 0, 155, 0) 25 | BRIGHTBLUE = ( 0, 0, 255) 26 | BLUE = ( 0, 0, 155) 27 | BRIGHTYELLOW = (255, 255, 0) 28 | YELLOW = (155, 155, 0) 29 | DARKGRAY = ( 40, 40, 40) 30 | bgColor = BLACK 31 | 32 | XMARGIN = int((WINDOWWIDTH - (2 * BUTTONSIZE) - BUTTONGAPSIZE) / 2) 33 | YMARGIN = int((WINDOWHEIGHT - (2 * BUTTONSIZE) - BUTTONGAPSIZE) / 2) 34 | 35 | # Rect objects for each of the four buttons 36 | YELLOWRECT = pygame.Rect(XMARGIN, YMARGIN, BUTTONSIZE, BUTTONSIZE) 37 | BLUERECT = pygame.Rect(XMARGIN + BUTTONSIZE + BUTTONGAPSIZE, YMARGIN, BUTTONSIZE, BUTTONSIZE) 38 | REDRECT = pygame.Rect(XMARGIN, YMARGIN + BUTTONSIZE + BUTTONGAPSIZE, BUTTONSIZE, BUTTONSIZE) 39 | GREENRECT = pygame.Rect(XMARGIN + BUTTONSIZE + BUTTONGAPSIZE, YMARGIN + BUTTONSIZE + BUTTONGAPSIZE, BUTTONSIZE, BUTTONSIZE) 40 | 41 | def main(): 42 | global FPSCLOCK, DISPLAYSURF, BASICFONT, BEEP1, BEEP2, BEEP3, BEEP4 43 | 44 | pygame.init() 45 | FPSCLOCK = pygame.time.Clock() 46 | DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) 47 | pygame.display.set_caption('Simulate') 48 | 49 | BASICFONT = pygame.font.Font('freesansbold.ttf', 16) 50 | infoSurf = BASICFONT.render('Match the pattern by clicking on the button or using the Q, W, A, S keys.', 1, DARKGRAY) 51 | infoRect = infoSurf.get_rect() 52 | infoRect.topleft = (10, WINDOWHEIGHT - 25) 53 | 54 | # load the sound files 55 | BEEP1 = pygame.mixer.Sound('beep1.ogg') 56 | BEEP2 = pygame.mixer.Sound('beep2.ogg') 57 | BEEP3 = pygame.mixer.Sound('beep3.ogg') 58 | BEEP4 = pygame.mixer.Sound('beep4.ogg') 59 | 60 | # Initialize some variables for a new game 61 | pattern = [] # stores the pattern of colors 62 | currentStep = 0 # the color the player must push next 63 | lastClickTime = 0 # timestamp of the player's last button push 64 | score = 0 65 | # when False, the pattern is playing. when True, waiting for the player to click a colored button: 66 | waitingForInput = False 67 | 68 | while True: # main game loop 69 | clickedButton = None # button that was clicked (set to YELLOW, RED, GREEN, or BLUE) 70 | DISPLAYSURF.fill(bgColor) 71 | drawButtons() 72 | 73 | scoreSurf = BASICFONT.render('Score: ' + str(score), 1, WHITE) 74 | scoreRect = scoreSurf.get_rect() 75 | scoreRect.topleft = (WINDOWWIDTH - 100, 10) 76 | DISPLAYSURF.blit(scoreSurf, scoreRect) 77 | 78 | DISPLAYSURF.blit(infoSurf, infoRect) 79 | 80 | checkForQuit() 81 | for event in pygame.event.get(): # event handling loop 82 | if event.type == MOUSEBUTTONUP: 83 | mousex, mousey = event.pos 84 | clickedButton = getButtonClicked(mousex, mousey) 85 | elif event.type == KEYDOWN: 86 | if event.key == K_q: 87 | clickedButton = YELLOW 88 | elif event.key == K_w: 89 | clickedButton = BLUE 90 | elif event.key == K_a: 91 | clickedButton = RED 92 | elif event.key == K_s: 93 | clickedButton = GREEN 94 | 95 | 96 | 97 | if not waitingForInput: 98 | # play the pattern 99 | pygame.display.update() 100 | pygame.time.wait(1000) 101 | pattern.append(random.choice((YELLOW, BLUE, RED, GREEN))) 102 | for button in pattern: 103 | flashButtonAnimation(button) 104 | pygame.time.wait(FLASHDELAY) 105 | waitingForInput = True 106 | else: 107 | # wait for the player to enter buttons 108 | if clickedButton and clickedButton == pattern[currentStep]: 109 | # pushed the correct button 110 | flashButtonAnimation(clickedButton) 111 | currentStep += 1 112 | lastClickTime = time.time() 113 | 114 | if currentStep == len(pattern): 115 | # pushed the last button in the pattern 116 | changeBackgroundAnimation() 117 | score += 1 118 | waitingForInput = False 119 | currentStep = 0 # reset back to first step 120 | 121 | elif (clickedButton and clickedButton != pattern[currentStep]) or (currentStep != 0 and time.time() - TIMEOUT > lastClickTime): 122 | # pushed the incorrect button, or has timed out 123 | gameOverAnimation() 124 | # reset the variables for a new game: 125 | pattern = [] 126 | currentStep = 0 127 | waitingForInput = False 128 | score = 0 129 | pygame.time.wait(1000) 130 | changeBackgroundAnimation() 131 | 132 | pygame.display.update() 133 | FPSCLOCK.tick(FPS) 134 | 135 | 136 | def terminate(): 137 | pygame.quit() 138 | sys.exit() 139 | 140 | 141 | def checkForQuit(): 142 | for event in pygame.event.get(QUIT): # get all the QUIT events 143 | terminate() # terminate if any QUIT events are present 144 | for event in pygame.event.get(KEYUP): # get all the KEYUP events 145 | if event.key == K_ESCAPE: 146 | terminate() # terminate if the KEYUP event was for the Esc key 147 | pygame.event.post(event) # put the other KEYUP event objects back 148 | 149 | 150 | def flashButtonAnimation(color, animationSpeed=50): 151 | if color == YELLOW: 152 | sound = BEEP1 153 | flashColor = BRIGHTYELLOW 154 | rectangle = YELLOWRECT 155 | elif color == BLUE: 156 | sound = BEEP2 157 | flashColor = BRIGHTBLUE 158 | rectangle = BLUERECT 159 | elif color == RED: 160 | sound = BEEP3 161 | flashColor = BRIGHTRED 162 | rectangle = REDRECT 163 | elif color == GREEN: 164 | sound = BEEP4 165 | flashColor = BRIGHTGREEN 166 | rectangle = GREENRECT 167 | 168 | origSurf = DISPLAYSURF.copy() 169 | flashSurf = pygame.Surface((BUTTONSIZE, BUTTONSIZE)) 170 | flashSurf = flashSurf.convert_alpha() 171 | r, g, b = flashColor 172 | sound.play() 173 | for start, end, step in ((0, 255, 1), (255, 0, -1)): # animation loop 174 | for alpha in range(start, end, animationSpeed * step): 175 | checkForQuit() 176 | DISPLAYSURF.blit(origSurf, (0, 0)) 177 | flashSurf.fill((r, g, b, alpha)) 178 | DISPLAYSURF.blit(flashSurf, rectangle.topleft) 179 | pygame.display.update() 180 | FPSCLOCK.tick(FPS) 181 | DISPLAYSURF.blit(origSurf, (0, 0)) 182 | 183 | 184 | def drawButtons(): 185 | pygame.draw.rect(DISPLAYSURF, YELLOW, YELLOWRECT) 186 | pygame.draw.rect(DISPLAYSURF, BLUE, BLUERECT) 187 | pygame.draw.rect(DISPLAYSURF, RED, REDRECT) 188 | pygame.draw.rect(DISPLAYSURF, GREEN, GREENRECT) 189 | 190 | 191 | def changeBackgroundAnimation(animationSpeed=40): 192 | global bgColor 193 | newBgColor = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) 194 | 195 | newBgSurf = pygame.Surface((WINDOWWIDTH, WINDOWHEIGHT)) 196 | newBgSurf = newBgSurf.convert_alpha() 197 | r, g, b = newBgColor 198 | for alpha in range(0, 255, animationSpeed): # animation loop 199 | checkForQuit() 200 | DISPLAYSURF.fill(bgColor) 201 | 202 | newBgSurf.fill((r, g, b, alpha)) 203 | DISPLAYSURF.blit(newBgSurf, (0, 0)) 204 | 205 | drawButtons() # redraw the buttons on top of the tint 206 | 207 | pygame.display.update() 208 | FPSCLOCK.tick(FPS) 209 | bgColor = newBgColor 210 | 211 | 212 | def gameOverAnimation(color=WHITE, animationSpeed=50): 213 | # play all beeps at once, then flash the background 214 | origSurf = DISPLAYSURF.copy() 215 | flashSurf = pygame.Surface(DISPLAYSURF.get_size()) 216 | flashSurf = flashSurf.convert_alpha() 217 | BEEP1.play() # play all four beeps at the same time, roughly. 218 | BEEP2.play() 219 | BEEP3.play() 220 | BEEP4.play() 221 | r, g, b = color 222 | for i in range(3): # do the flash 3 times 223 | for start, end, step in ((0, 255, 1), (255, 0, -1)): 224 | # The first iteration in this loop sets the following for loop 225 | # to go from 0 to 255, the second from 255 to 0. 226 | for alpha in range(start, end, animationSpeed * step): # animation loop 227 | # alpha means transparency. 255 is opaque, 0 is invisible 228 | checkForQuit() 229 | flashSurf.fill((r, g, b, alpha)) 230 | DISPLAYSURF.blit(origSurf, (0, 0)) 231 | DISPLAYSURF.blit(flashSurf, (0, 0)) 232 | drawButtons() 233 | pygame.display.update() 234 | FPSCLOCK.tick(FPS) 235 | 236 | 237 | 238 | def getButtonClicked(x, y): 239 | if YELLOWRECT.collidepoint( (x, y) ): 240 | return YELLOW 241 | elif BLUERECT.collidepoint( (x, y) ): 242 | return BLUE 243 | elif REDRECT.collidepoint( (x, y) ): 244 | return RED 245 | elif GREENRECT.collidepoint( (x, y) ): 246 | return GREEN 247 | return None 248 | 249 | 250 | if __name__ == '__main__': 251 | main() 252 | -------------------------------------------------------------------------------- /memorypuzzle/memorypuzzle.py: -------------------------------------------------------------------------------- 1 | # Memory Puzzle 2 | # By Al Sweigart al@inventwithpython.com 3 | # http://inventwithpython.com/pygame 4 | # Released under a "Simplified BSD" license 5 | 6 | import random, pygame, sys 7 | from pygame.locals import * 8 | 9 | FPS = 30 # frames per second, the general speed of the program 10 | WINDOWWIDTH = 640 # size of window's width in pixels 11 | WINDOWHEIGHT = 480 # size of windows' height in pixels 12 | REVEALSPEED = 8 # speed boxes' sliding reveals and covers 13 | BOXSIZE = 40 # size of box height & width in pixels 14 | GAPSIZE = 10 # size of gap between boxes in pixels 15 | BOARDWIDTH = 10 # number of columns of icons 16 | BOARDHEIGHT = 7 # number of rows of icons 17 | assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.' 18 | XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2) 19 | YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2) 20 | 21 | # R G B 22 | GRAY = (100, 100, 100) 23 | NAVYBLUE = ( 60, 60, 100) 24 | WHITE = (255, 255, 255) 25 | RED = (255, 0, 0) 26 | GREEN = ( 0, 255, 0) 27 | BLUE = ( 0, 0, 255) 28 | YELLOW = (255, 255, 0) 29 | ORANGE = (255, 128, 0) 30 | PURPLE = (255, 0, 255) 31 | CYAN = ( 0, 255, 255) 32 | 33 | BGCOLOR = NAVYBLUE 34 | LIGHTBGCOLOR = GRAY 35 | BOXCOLOR = WHITE 36 | HIGHLIGHTCOLOR = BLUE 37 | 38 | DONUT = 'donut' 39 | SQUARE = 'square' 40 | DIAMOND = 'diamond' 41 | LINES = 'lines' 42 | OVAL = 'oval' 43 | 44 | ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN) 45 | ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL) 46 | assert len(ALLCOLORS) * len(ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined." 47 | 48 | def main(): 49 | global FPSCLOCK, DISPLAYSURF 50 | pygame.init() 51 | FPSCLOCK = pygame.time.Clock() 52 | DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) 53 | 54 | mousex = 0 # used to store x coordinate of mouse event 55 | mousey = 0 # used to store y coordinate of mouse event 56 | pygame.display.set_caption('Memory Game') 57 | 58 | mainBoard = getRandomizedBoard() 59 | revealedBoxes = generateRevealedBoxesData(False) 60 | 61 | firstSelection = None # stores the (x, y) of the first box clicked. 62 | 63 | DISPLAYSURF.fill(BGCOLOR) 64 | startGameAnimation(mainBoard) 65 | 66 | while True: # main game loop 67 | mouseClicked = False 68 | 69 | DISPLAYSURF.fill(BGCOLOR) # drawing the window 70 | drawBoard(mainBoard, revealedBoxes) 71 | 72 | for event in pygame.event.get(): # event handling loop 73 | if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE): 74 | pygame.quit() 75 | sys.exit() 76 | elif event.type == MOUSEMOTION: 77 | mousex, mousey = event.pos 78 | elif event.type == MOUSEBUTTONUP: 79 | mousex, mousey = event.pos 80 | mouseClicked = True 81 | 82 | boxx, boxy = getBoxAtPixel(mousex, mousey) 83 | if boxx != None and boxy != None: 84 | # The mouse is currently over a box. 85 | if not revealedBoxes[boxx][boxy]: 86 | drawHighlightBox(boxx, boxy) 87 | if not revealedBoxes[boxx][boxy] and mouseClicked: 88 | revealBoxesAnimation(mainBoard, [(boxx, boxy)]) 89 | revealedBoxes[boxx][boxy] = True # set the box as "revealed" 90 | if firstSelection == None: # the current box was the first box clicked 91 | firstSelection = (boxx, boxy) 92 | else: # the current box was the second box clicked 93 | # Check if there is a match between the two icons. 94 | icon1shape, icon1color = getShapeAndColor(mainBoard, firstSelection[0], firstSelection[1]) 95 | icon2shape, icon2color = getShapeAndColor(mainBoard, boxx, boxy) 96 | 97 | if icon1shape != icon2shape or icon1color != icon2color: 98 | # Icons don't match. Re-cover up both selections. 99 | pygame.time.wait(1000) # 1000 milliseconds = 1 sec 100 | coverBoxesAnimation(mainBoard, [(firstSelection[0], firstSelection[1]), (boxx, boxy)]) 101 | revealedBoxes[firstSelection[0]][firstSelection[1]] = False 102 | revealedBoxes[boxx][boxy] = False 103 | elif hasWon(revealedBoxes): # check if all pairs found 104 | gameWonAnimation(mainBoard) 105 | pygame.time.wait(2000) 106 | 107 | # Reset the board 108 | mainBoard = getRandomizedBoard() 109 | revealedBoxes = generateRevealedBoxesData(False) 110 | 111 | # Show the fully unrevealed board for a second. 112 | drawBoard(mainBoard, revealedBoxes) 113 | pygame.display.update() 114 | pygame.time.wait(1000) 115 | 116 | # Replay the start game animation. 117 | startGameAnimation(mainBoard) 118 | firstSelection = None # reset firstSelection variable 119 | 120 | # Redraw the screen and wait a clock tick. 121 | pygame.display.update() 122 | FPSCLOCK.tick(FPS) 123 | 124 | 125 | def generateRevealedBoxesData(val): 126 | revealedBoxes = [] 127 | for i in range(BOARDWIDTH): 128 | revealedBoxes.append([val] * BOARDHEIGHT) 129 | return revealedBoxes 130 | 131 | 132 | def getRandomizedBoard(): 133 | # Get a list of every possible shape in every possible color. 134 | icons = [] 135 | for color in ALLCOLORS: 136 | for shape in ALLSHAPES: 137 | icons.append( (shape, color) ) 138 | 139 | random.shuffle(icons) # randomize the order of the icons list 140 | numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2) # calculate how many icons are needed 141 | icons = icons[:numIconsUsed] * 2 # make two of each 142 | random.shuffle(icons) 143 | 144 | # Create the board data structure, with randomly placed icons. 145 | board = [] 146 | for x in range(BOARDWIDTH): 147 | column = [] 148 | for y in range(BOARDHEIGHT): 149 | column.append(icons[0]) 150 | del icons[0] # remove the icons as we assign them 151 | board.append(column) 152 | return board 153 | 154 | 155 | def splitIntoGroupsOf(groupSize, theList): 156 | # splits a list into a list of lists, where the inner lists have at 157 | # most groupSize number of items. 158 | result = [] 159 | for i in range(0, len(theList), groupSize): 160 | result.append(theList[i:i + groupSize]) 161 | return result 162 | 163 | 164 | def leftTopCoordsOfBox(boxx, boxy): 165 | # Convert board coordinates to pixel coordinates 166 | left = boxx * (BOXSIZE + GAPSIZE) + XMARGIN 167 | top = boxy * (BOXSIZE + GAPSIZE) + YMARGIN 168 | return (left, top) 169 | 170 | 171 | def getBoxAtPixel(x, y): 172 | for boxx in range(BOARDWIDTH): 173 | for boxy in range(BOARDHEIGHT): 174 | left, top = leftTopCoordsOfBox(boxx, boxy) 175 | boxRect = pygame.Rect(left, top, BOXSIZE, BOXSIZE) 176 | if boxRect.collidepoint(x, y): 177 | return (boxx, boxy) 178 | return (None, None) 179 | 180 | 181 | def drawIcon(shape, color, boxx, boxy): 182 | quarter = int(BOXSIZE * 0.25) # syntactic sugar 183 | half = int(BOXSIZE * 0.5) # syntactic sugar 184 | 185 | left, top = leftTopCoordsOfBox(boxx, boxy) # get pixel coords from board coords 186 | # Draw the shapes 187 | if shape == DONUT: 188 | pygame.draw.circle(DISPLAYSURF, color, (left + half, top + half), half - 5) 189 | pygame.draw.circle(DISPLAYSURF, BGCOLOR, (left + half, top + half), quarter - 5) 190 | elif shape == SQUARE: 191 | pygame.draw.rect(DISPLAYSURF, color, (left + quarter, top + quarter, BOXSIZE - half, BOXSIZE - half)) 192 | elif shape == DIAMOND: 193 | pygame.draw.polygon(DISPLAYSURF, color, ((left + half, top), (left + BOXSIZE - 1, top + half), (left + half, top + BOXSIZE - 1), (left, top + half))) 194 | elif shape == LINES: 195 | for i in range(0, BOXSIZE, 4): 196 | pygame.draw.line(DISPLAYSURF, color, (left, top + i), (left + i, top)) 197 | pygame.draw.line(DISPLAYSURF, color, (left + i, top + BOXSIZE - 1), (left + BOXSIZE - 1, top + i)) 198 | elif shape == OVAL: 199 | pygame.draw.ellipse(DISPLAYSURF, color, (left, top + quarter, BOXSIZE, half)) 200 | 201 | 202 | def getShapeAndColor(board, boxx, boxy): 203 | # shape value for x, y spot is stored in board[x][y][0] 204 | # color value for x, y spot is stored in board[x][y][1] 205 | return board[boxx][boxy][0], board[boxx][boxy][1] 206 | 207 | 208 | def drawBoxCovers(board, boxes, coverage): 209 | # Draws boxes being covered/revealed. "boxes" is a list 210 | # of two-item lists, which have the x & y spot of the box. 211 | for box in boxes: 212 | left, top = leftTopCoordsOfBox(box[0], box[1]) 213 | pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, BOXSIZE, BOXSIZE)) 214 | shape, color = getShapeAndColor(board, box[0], box[1]) 215 | drawIcon(shape, color, box[0], box[1]) 216 | if coverage > 0: # only draw the cover if there is an coverage 217 | pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, coverage, BOXSIZE)) 218 | pygame.display.update() 219 | FPSCLOCK.tick(FPS) 220 | 221 | 222 | def revealBoxesAnimation(board, boxesToReveal): 223 | # Do the "box reveal" animation. 224 | for coverage in range(BOXSIZE, (-REVEALSPEED) - 1, -REVEALSPEED): 225 | drawBoxCovers(board, boxesToReveal, coverage) 226 | 227 | 228 | def coverBoxesAnimation(board, boxesToCover): 229 | # Do the "box cover" animation. 230 | for coverage in range(0, BOXSIZE + REVEALSPEED, REVEALSPEED): 231 | drawBoxCovers(board, boxesToCover, coverage) 232 | 233 | 234 | def drawBoard(board, revealed): 235 | # Draws all of the boxes in their covered or revealed state. 236 | for boxx in range(BOARDWIDTH): 237 | for boxy in range(BOARDHEIGHT): 238 | left, top = leftTopCoordsOfBox(boxx, boxy) 239 | if not revealed[boxx][boxy]: 240 | # Draw a covered box. 241 | pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, BOXSIZE, BOXSIZE)) 242 | else: 243 | # Draw the (revealed) icon. 244 | shape, color = getShapeAndColor(board, boxx, boxy) 245 | drawIcon(shape, color, boxx, boxy) 246 | 247 | 248 | def drawHighlightBox(boxx, boxy): 249 | left, top = leftTopCoordsOfBox(boxx, boxy) 250 | pygame.draw.rect(DISPLAYSURF, HIGHLIGHTCOLOR, (left - 5, top - 5, BOXSIZE + 10, BOXSIZE + 10), 4) 251 | 252 | 253 | def startGameAnimation(board): 254 | # Randomly reveal the boxes 8 at a time. 255 | coveredBoxes = generateRevealedBoxesData(False) 256 | boxes = [] 257 | for x in range(BOARDWIDTH): 258 | for y in range(BOARDHEIGHT): 259 | boxes.append( (x, y) ) 260 | random.shuffle(boxes) 261 | boxGroups = splitIntoGroupsOf(8, boxes) 262 | 263 | drawBoard(board, coveredBoxes) 264 | for boxGroup in boxGroups: 265 | revealBoxesAnimation(board, boxGroup) 266 | coverBoxesAnimation(board, boxGroup) 267 | 268 | 269 | def gameWonAnimation(board): 270 | # flash the background color when the player has won 271 | coveredBoxes = generateRevealedBoxesData(True) 272 | color1 = LIGHTBGCOLOR 273 | color2 = BGCOLOR 274 | 275 | for i in range(13): 276 | color1, color2 = color2, color1 # swap colors 277 | DISPLAYSURF.fill(color1) 278 | drawBoard(board, coveredBoxes) 279 | pygame.display.update() 280 | pygame.time.wait(300) 281 | 282 | 283 | def hasWon(revealedBoxes): 284 | # Returns True if all the boxes have been revealed, otherwise False 285 | for i in revealedBoxes: 286 | if False in i: 287 | return False # return False if any boxes are covered. 288 | return True 289 | 290 | 291 | if __name__ == '__main__': 292 | main() -------------------------------------------------------------------------------- /slidepuzzle/slidepuzzle.py: -------------------------------------------------------------------------------- 1 | # Slide Puzzle 2 | # By Al Sweigart al@inventwithpython.com 3 | # http://inventwithpython.com/pygame 4 | # Released under a "Simplified BSD" license 5 | 6 | import pygame, sys, random 7 | from pygame.locals import * 8 | 9 | # Create the constants (go ahead and experiment with different values) 10 | BOARDWIDTH = 4 # number of columns in the board 11 | BOARDHEIGHT = 4 # number of rows in the board 12 | TILESIZE = 80 13 | WINDOWWIDTH = 640 14 | WINDOWHEIGHT = 480 15 | FPS = 30 16 | BLANK = None 17 | 18 | # R G B 19 | BLACK = ( 0, 0, 0) 20 | WHITE = (255, 255, 255) 21 | BRIGHTBLUE = ( 0, 50, 255) 22 | DARKTURQUOISE = ( 3, 54, 73) 23 | GREEN = ( 0, 204, 0) 24 | 25 | BGCOLOR = DARKTURQUOISE 26 | TILECOLOR = GREEN 27 | TEXTCOLOR = WHITE 28 | BORDERCOLOR = BRIGHTBLUE 29 | BASICFONTSIZE = 20 30 | 31 | BUTTONCOLOR = WHITE 32 | BUTTONTEXTCOLOR = BLACK 33 | MESSAGECOLOR = WHITE 34 | 35 | XMARGIN = int((WINDOWWIDTH - (TILESIZE * BOARDWIDTH + (BOARDWIDTH - 1))) / 2) 36 | YMARGIN = int((WINDOWHEIGHT - (TILESIZE * BOARDHEIGHT + (BOARDHEIGHT - 1))) / 2) 37 | 38 | UP = 'up' 39 | DOWN = 'down' 40 | LEFT = 'left' 41 | RIGHT = 'right' 42 | 43 | def main(): 44 | global FPSCLOCK, DISPLAYSURF, BASICFONT, RESET_SURF, RESET_RECT, NEW_SURF, NEW_RECT, SOLVE_SURF, SOLVE_RECT 45 | 46 | pygame.init() 47 | FPSCLOCK = pygame.time.Clock() 48 | DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) 49 | pygame.display.set_caption('Slide Puzzle') 50 | BASICFONT = pygame.font.Font('freesansbold.ttf', BASICFONTSIZE) 51 | 52 | # Store the option buttons and their rectangles in OPTIONS. 53 | RESET_SURF, RESET_RECT = makeText('Reset', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 90) 54 | NEW_SURF, NEW_RECT = makeText('New Game', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 60) 55 | SOLVE_SURF, SOLVE_RECT = makeText('Solve', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 30) 56 | 57 | mainBoard, solutionSeq = generateNewPuzzle(80) 58 | SOLVEDBOARD = getStartingBoard() # a solved board is the same as the board in a start state. 59 | allMoves = [] # list of moves made from the solved configuration 60 | 61 | while True: # main game loop 62 | slideTo = None # the direction, if any, a tile should slide 63 | msg = 'Click tile or press arrow keys to slide.' # contains the message to show in the upper left corner. 64 | if mainBoard == SOLVEDBOARD: 65 | msg = 'Solved!' 66 | 67 | drawBoard(mainBoard, msg) 68 | 69 | checkForQuit() 70 | for event in pygame.event.get(): # event handling loop 71 | if event.type == MOUSEBUTTONUP: 72 | spotx, spoty = getSpotClicked(mainBoard, event.pos[0], event.pos[1]) 73 | 74 | if (spotx, spoty) == (None, None): 75 | # check if the user clicked on an option button 76 | if RESET_RECT.collidepoint(event.pos): 77 | resetAnimation(mainBoard, allMoves) # clicked on Reset button 78 | allMoves = [] 79 | elif NEW_RECT.collidepoint(event.pos): 80 | mainBoard, solutionSeq = generateNewPuzzle(80) # clicked on New Game button 81 | allMoves = [] 82 | elif SOLVE_RECT.collidepoint(event.pos): 83 | resetAnimation(mainBoard, solutionSeq + allMoves) # clicked on Solve button 84 | allMoves = [] 85 | else: 86 | # check if the clicked tile was next to the blank spot 87 | 88 | blankx, blanky = getBlankPosition(mainBoard) 89 | if spotx == blankx + 1 and spoty == blanky: 90 | slideTo = LEFT 91 | elif spotx == blankx - 1 and spoty == blanky: 92 | slideTo = RIGHT 93 | elif spotx == blankx and spoty == blanky + 1: 94 | slideTo = UP 95 | elif spotx == blankx and spoty == blanky - 1: 96 | slideTo = DOWN 97 | 98 | elif event.type == KEYUP: 99 | # check if the user pressed a key to slide a tile 100 | if event.key in (K_LEFT, K_a) and isValidMove(mainBoard, LEFT): 101 | slideTo = LEFT 102 | elif event.key in (K_RIGHT, K_d) and isValidMove(mainBoard, RIGHT): 103 | slideTo = RIGHT 104 | elif event.key in (K_UP, K_w) and isValidMove(mainBoard, UP): 105 | slideTo = UP 106 | elif event.key in (K_DOWN, K_s) and isValidMove(mainBoard, DOWN): 107 | slideTo = DOWN 108 | 109 | if slideTo: 110 | slideAnimation(mainBoard, slideTo, 'Click tile or press arrow keys to slide.', 8) # show slide on screen 111 | makeMove(mainBoard, slideTo) 112 | allMoves.append(slideTo) # record the slide 113 | pygame.display.update() 114 | FPSCLOCK.tick(FPS) 115 | 116 | 117 | def terminate(): 118 | pygame.quit() 119 | sys.exit() 120 | 121 | 122 | def checkForQuit(): 123 | for event in pygame.event.get(QUIT): # get all the QUIT events 124 | terminate() # terminate if any QUIT events are present 125 | for event in pygame.event.get(KEYUP): # get all the KEYUP events 126 | if event.key == K_ESCAPE: 127 | terminate() # terminate if the KEYUP event was for the Esc key 128 | pygame.event.post(event) # put the other KEYUP event objects back 129 | 130 | 131 | def getStartingBoard(): 132 | # Return a board data structure with tiles in the solved state. 133 | # For example, if BOARDWIDTH and BOARDHEIGHT are both 3, this function 134 | # returns [[1, 4, 7], [2, 5, 8], [3, 6, BLANK]] 135 | counter = 1 136 | board = [] 137 | for x in range(BOARDWIDTH): 138 | column = [] 139 | for y in range(BOARDHEIGHT): 140 | column.append(counter) 141 | counter += BOARDWIDTH 142 | board.append(column) 143 | counter -= BOARDWIDTH * (BOARDHEIGHT - 1) + BOARDWIDTH - 1 144 | 145 | board[BOARDWIDTH-1][BOARDHEIGHT-1] = BLANK 146 | return board 147 | 148 | 149 | def getBlankPosition(board): 150 | # Return the x and y of board coordinates of the blank space. 151 | for x in range(BOARDWIDTH): 152 | for y in range(BOARDHEIGHT): 153 | if board[x][y] == BLANK: 154 | return (x, y) 155 | 156 | 157 | def makeMove(board, move): 158 | # This function does not check if the move is valid. 159 | blankx, blanky = getBlankPosition(board) 160 | 161 | if move == UP: 162 | board[blankx][blanky], board[blankx][blanky + 1] = board[blankx][blanky + 1], board[blankx][blanky] 163 | elif move == DOWN: 164 | board[blankx][blanky], board[blankx][blanky - 1] = board[blankx][blanky - 1], board[blankx][blanky] 165 | elif move == LEFT: 166 | board[blankx][blanky], board[blankx + 1][blanky] = board[blankx + 1][blanky], board[blankx][blanky] 167 | elif move == RIGHT: 168 | board[blankx][blanky], board[blankx - 1][blanky] = board[blankx - 1][blanky], board[blankx][blanky] 169 | 170 | 171 | def isValidMove(board, move): 172 | blankx, blanky = getBlankPosition(board) 173 | return (move == UP and blanky != len(board[0]) - 1) or \ 174 | (move == DOWN and blanky != 0) or \ 175 | (move == LEFT and blankx != len(board) - 1) or \ 176 | (move == RIGHT and blankx != 0) 177 | 178 | 179 | def getRandomMove(board, lastMove=None): 180 | # start with a full list of all four moves 181 | validMoves = [UP, DOWN, LEFT, RIGHT] 182 | 183 | # remove moves from the list as they are disqualified 184 | if lastMove == UP or not isValidMove(board, DOWN): 185 | validMoves.remove(DOWN) 186 | if lastMove == DOWN or not isValidMove(board, UP): 187 | validMoves.remove(UP) 188 | if lastMove == LEFT or not isValidMove(board, RIGHT): 189 | validMoves.remove(RIGHT) 190 | if lastMove == RIGHT or not isValidMove(board, LEFT): 191 | validMoves.remove(LEFT) 192 | 193 | # return a random move from the list of remaining moves 194 | return random.choice(validMoves) 195 | 196 | 197 | def getLeftTopOfTile(tileX, tileY): 198 | left = XMARGIN + (tileX * TILESIZE) + (tileX - 1) 199 | top = YMARGIN + (tileY * TILESIZE) + (tileY - 1) 200 | return (left, top) 201 | 202 | 203 | def getSpotClicked(board, x, y): 204 | # from the x & y pixel coordinates, get the x & y board coordinates 205 | for tileX in range(len(board)): 206 | for tileY in range(len(board[0])): 207 | left, top = getLeftTopOfTile(tileX, tileY) 208 | tileRect = pygame.Rect(left, top, TILESIZE, TILESIZE) 209 | if tileRect.collidepoint(x, y): 210 | return (tileX, tileY) 211 | return (None, None) 212 | 213 | 214 | def drawTile(tilex, tiley, number, adjx=0, adjy=0): 215 | # draw a tile at board coordinates tilex and tiley, optionally a few 216 | # pixels over (determined by adjx and adjy) 217 | left, top = getLeftTopOfTile(tilex, tiley) 218 | pygame.draw.rect(DISPLAYSURF, TILECOLOR, (left + adjx, top + adjy, TILESIZE, TILESIZE)) 219 | textSurf = BASICFONT.render(str(number), True, TEXTCOLOR) 220 | textRect = textSurf.get_rect() 221 | textRect.center = left + int(TILESIZE / 2) + adjx, top + int(TILESIZE / 2) + adjy 222 | DISPLAYSURF.blit(textSurf, textRect) 223 | 224 | 225 | def makeText(text, color, bgcolor, top, left): 226 | # create the Surface and Rect objects for some text. 227 | textSurf = BASICFONT.render(text, True, color, bgcolor) 228 | textRect = textSurf.get_rect() 229 | textRect.topleft = (top, left) 230 | return (textSurf, textRect) 231 | 232 | 233 | def drawBoard(board, message): 234 | DISPLAYSURF.fill(BGCOLOR) 235 | if message: 236 | textSurf, textRect = makeText(message, MESSAGECOLOR, BGCOLOR, 5, 5) 237 | DISPLAYSURF.blit(textSurf, textRect) 238 | 239 | for tilex in range(len(board)): 240 | for tiley in range(len(board[0])): 241 | if board[tilex][tiley]: 242 | drawTile(tilex, tiley, board[tilex][tiley]) 243 | 244 | left, top = getLeftTopOfTile(0, 0) 245 | width = BOARDWIDTH * TILESIZE 246 | height = BOARDHEIGHT * TILESIZE 247 | pygame.draw.rect(DISPLAYSURF, BORDERCOLOR, (left - 5, top - 5, width + 11, height + 11), 4) 248 | 249 | DISPLAYSURF.blit(RESET_SURF, RESET_RECT) 250 | DISPLAYSURF.blit(NEW_SURF, NEW_RECT) 251 | DISPLAYSURF.blit(SOLVE_SURF, SOLVE_RECT) 252 | 253 | 254 | def slideAnimation(board, direction, message, animationSpeed): 255 | # Note: This function does not check if the move is valid. 256 | 257 | blankx, blanky = getBlankPosition(board) 258 | if direction == UP: 259 | movex = blankx 260 | movey = blanky + 1 261 | elif direction == DOWN: 262 | movex = blankx 263 | movey = blanky - 1 264 | elif direction == LEFT: 265 | movex = blankx + 1 266 | movey = blanky 267 | elif direction == RIGHT: 268 | movex = blankx - 1 269 | movey = blanky 270 | 271 | # prepare the base surface 272 | drawBoard(board, message) 273 | baseSurf = DISPLAYSURF.copy() 274 | # draw a blank space over the moving tile on the baseSurf Surface. 275 | moveLeft, moveTop = getLeftTopOfTile(movex, movey) 276 | pygame.draw.rect(baseSurf, BGCOLOR, (moveLeft, moveTop, TILESIZE, TILESIZE)) 277 | 278 | for i in range(0, TILESIZE, animationSpeed): 279 | # animate the tile sliding over 280 | checkForQuit() 281 | DISPLAYSURF.blit(baseSurf, (0, 0)) 282 | if direction == UP: 283 | drawTile(movex, movey, board[movex][movey], 0, -i) 284 | if direction == DOWN: 285 | drawTile(movex, movey, board[movex][movey], 0, i) 286 | if direction == LEFT: 287 | drawTile(movex, movey, board[movex][movey], -i, 0) 288 | if direction == RIGHT: 289 | drawTile(movex, movey, board[movex][movey], i, 0) 290 | 291 | pygame.display.update() 292 | FPSCLOCK.tick(FPS) 293 | 294 | 295 | def generateNewPuzzle(numSlides): 296 | # From a starting configuration, make numSlides number of moves (and 297 | # animate these moves). 298 | sequence = [] 299 | board = getStartingBoard() 300 | drawBoard(board, '') 301 | pygame.display.update() 302 | pygame.time.wait(500) # pause 500 milliseconds for effect 303 | lastMove = None 304 | for i in range(numSlides): 305 | move = getRandomMove(board, lastMove) 306 | slideAnimation(board, move, 'Generating new puzzle...', animationSpeed=int(TILESIZE / 3)) 307 | makeMove(board, move) 308 | sequence.append(move) 309 | lastMove = move 310 | return (board, sequence) 311 | 312 | 313 | def resetAnimation(board, allMoves): 314 | # make all of the moves in allMoves in reverse. 315 | revAllMoves = allMoves[:] # gets a copy of the list 316 | revAllMoves.reverse() 317 | 318 | for move in revAllMoves: 319 | if move == UP: 320 | oppositeMove = DOWN 321 | elif move == DOWN: 322 | oppositeMove = UP 323 | elif move == RIGHT: 324 | oppositeMove = LEFT 325 | elif move == LEFT: 326 | oppositeMove = RIGHT 327 | slideAnimation(board, oppositeMove, '', animationSpeed=int(TILESIZE / 2)) 328 | makeMove(board, oppositeMove) 329 | 330 | 331 | if __name__ == '__main__': 332 | main() -------------------------------------------------------------------------------- /fourinarow/fourinarow.py: -------------------------------------------------------------------------------- 1 | # Four-In-A-Row (a Connect Four clone) 2 | # By Al Sweigart al@inventwithpython.com 3 | # http://inventwithpython.com/pygame 4 | # Released under a "Simplified BSD" license 5 | 6 | import random, copy, sys, pygame 7 | from pygame.locals import * 8 | 9 | BOARDWIDTH = 7 # how many spaces wide the board is 10 | BOARDHEIGHT = 6 # how many spaces tall the board is 11 | assert BOARDWIDTH >= 4 and BOARDHEIGHT >= 4, 'Board must be at least 4x4.' 12 | 13 | DIFFICULTY = 2 # how many moves to look ahead. (>2 is usually too much) 14 | 15 | SPACESIZE = 50 # size of the tokens and individual board spaces in pixels 16 | 17 | FPS = 30 # frames per second to update the screen 18 | WINDOWWIDTH = 640 # width of the program's window, in pixels 19 | WINDOWHEIGHT = 480 # height in pixels 20 | 21 | XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * SPACESIZE) / 2) 22 | YMARGIN = int((WINDOWHEIGHT - BOARDHEIGHT * SPACESIZE) / 2) 23 | 24 | BRIGHTBLUE = (0, 50, 255) 25 | WHITE = (255, 255, 255) 26 | 27 | BGCOLOR = BRIGHTBLUE 28 | TEXTCOLOR = WHITE 29 | 30 | RED = 'red' 31 | BLACK = 'black' 32 | EMPTY = None 33 | HUMAN = 'human' 34 | COMPUTER = 'computer' 35 | 36 | 37 | def main(): 38 | global FPSCLOCK, DISPLAYSURF, REDPILERECT, BLACKPILERECT, REDTOKENIMG 39 | global BLACKTOKENIMG, BOARDIMG, ARROWIMG, ARROWRECT, HUMANWINNERIMG 40 | global COMPUTERWINNERIMG, WINNERRECT, TIEWINNERIMG 41 | 42 | pygame.init() 43 | FPSCLOCK = pygame.time.Clock() 44 | DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) 45 | pygame.display.set_caption('Four in a Row') 46 | 47 | REDPILERECT = pygame.Rect(int(SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE) 48 | BLACKPILERECT = pygame.Rect(WINDOWWIDTH - int(3 * SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE) 49 | REDTOKENIMG = pygame.image.load('4row_red.png') 50 | REDTOKENIMG = pygame.transform.smoothscale(REDTOKENIMG, (SPACESIZE, SPACESIZE)) 51 | BLACKTOKENIMG = pygame.image.load('4row_black.png') 52 | BLACKTOKENIMG = pygame.transform.smoothscale(BLACKTOKENIMG, (SPACESIZE, SPACESIZE)) 53 | BOARDIMG = pygame.image.load('4row_board.png') 54 | BOARDIMG = pygame.transform.smoothscale(BOARDIMG, (SPACESIZE, SPACESIZE)) 55 | 56 | HUMANWINNERIMG = pygame.image.load('4row_humanwinner.png') 57 | COMPUTERWINNERIMG = pygame.image.load('4row_computerwinner.png') 58 | TIEWINNERIMG = pygame.image.load('4row_tie.png') 59 | WINNERRECT = HUMANWINNERIMG.get_rect() 60 | WINNERRECT.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2)) 61 | 62 | ARROWIMG = pygame.image.load('4row_arrow.png') 63 | ARROWRECT = ARROWIMG.get_rect() 64 | ARROWRECT.left = REDPILERECT.right + 10 65 | ARROWRECT.centery = REDPILERECT.centery 66 | 67 | isFirstGame = True 68 | 69 | while True: 70 | runGame(isFirstGame) 71 | isFirstGame = False 72 | 73 | 74 | def runGame(isFirstGame): 75 | if isFirstGame: 76 | # Let the computer go first on the first game, so the player 77 | # can see how the tokens are dragged from the token piles. 78 | turn = COMPUTER 79 | showHelp = True 80 | else: 81 | # Randomly choose who goes first. 82 | if random.randint(0, 1) == 0: 83 | turn = COMPUTER 84 | else: 85 | turn = HUMAN 86 | showHelp = False 87 | 88 | # Set up a blank board data structure. 89 | mainBoard = getNewBoard() 90 | 91 | while True: # main game loop 92 | if turn == HUMAN: 93 | # Human player's turn. 94 | getHumanMove(mainBoard, showHelp) 95 | if showHelp: 96 | # turn off help arrow after the first move 97 | showHelp = False 98 | if isWinner(mainBoard, RED): 99 | winnerImg = HUMANWINNERIMG 100 | break 101 | turn = COMPUTER # switch to other player's turn 102 | else: 103 | # Computer player's turn. 104 | column = getComputerMove(mainBoard) 105 | animateComputerMoving(mainBoard, column) 106 | makeMove(mainBoard, BLACK, column) 107 | if isWinner(mainBoard, BLACK): 108 | winnerImg = COMPUTERWINNERIMG 109 | break 110 | turn = HUMAN # switch to other player's turn 111 | 112 | if isBoardFull(mainBoard): 113 | # A completely filled board means it's a tie. 114 | winnerImg = TIEWINNERIMG 115 | break 116 | 117 | while True: 118 | # Keep looping until player clicks the mouse or quits. 119 | drawBoard(mainBoard) 120 | DISPLAYSURF.blit(winnerImg, WINNERRECT) 121 | pygame.display.update() 122 | FPSCLOCK.tick() 123 | for event in pygame.event.get(): # event handling loop 124 | if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE): 125 | pygame.quit() 126 | sys.exit() 127 | elif event.type == MOUSEBUTTONUP: 128 | return 129 | 130 | 131 | def makeMove(board, player, column): 132 | lowest = getLowestEmptySpace(board, column) 133 | if lowest != -1: 134 | board[column][lowest] = player 135 | 136 | 137 | def drawBoard(board, extraToken=None): 138 | DISPLAYSURF.fill(BGCOLOR) 139 | 140 | # draw tokens 141 | spaceRect = pygame.Rect(0, 0, SPACESIZE, SPACESIZE) 142 | for x in range(BOARDWIDTH): 143 | for y in range(BOARDHEIGHT): 144 | spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE)) 145 | if board[x][y] == RED: 146 | DISPLAYSURF.blit(REDTOKENIMG, spaceRect) 147 | elif board[x][y] == BLACK: 148 | DISPLAYSURF.blit(BLACKTOKENIMG, spaceRect) 149 | 150 | # draw the extra token 151 | if extraToken != None: 152 | if extraToken['color'] == RED: 153 | DISPLAYSURF.blit(REDTOKENIMG, (extraToken['x'], extraToken['y'], SPACESIZE, SPACESIZE)) 154 | elif extraToken['color'] == BLACK: 155 | DISPLAYSURF.blit(BLACKTOKENIMG, (extraToken['x'], extraToken['y'], SPACESIZE, SPACESIZE)) 156 | 157 | # draw board over the tokens 158 | for x in range(BOARDWIDTH): 159 | for y in range(BOARDHEIGHT): 160 | spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE)) 161 | DISPLAYSURF.blit(BOARDIMG, spaceRect) 162 | 163 | # draw the red and black tokens off to the side 164 | DISPLAYSURF.blit(REDTOKENIMG, REDPILERECT) # red on the left 165 | DISPLAYSURF.blit(BLACKTOKENIMG, BLACKPILERECT) # black on the right 166 | 167 | 168 | def getNewBoard(): 169 | board = [] 170 | for x in range(BOARDWIDTH): 171 | board.append([EMPTY] * BOARDHEIGHT) 172 | return board 173 | 174 | 175 | def getHumanMove(board, isFirstMove): 176 | draggingToken = False 177 | tokenx, tokeny = None, None 178 | while True: 179 | for event in pygame.event.get(): # event handling loop 180 | if event.type == QUIT: 181 | pygame.quit() 182 | sys.exit() 183 | elif event.type == MOUSEBUTTONDOWN and not draggingToken and REDPILERECT.collidepoint(event.pos): 184 | # start of dragging on red token pile. 185 | draggingToken = True 186 | tokenx, tokeny = event.pos 187 | elif event.type == MOUSEMOTION and draggingToken: 188 | # update the position of the red token being dragged 189 | tokenx, tokeny = event.pos 190 | elif event.type == MOUSEBUTTONUP and draggingToken: 191 | # let go of the token being dragged 192 | if tokeny < YMARGIN and tokenx > XMARGIN and tokenx < WINDOWWIDTH - XMARGIN: 193 | # let go at the top of the screen. 194 | column = int((tokenx - XMARGIN) / SPACESIZE) 195 | if isValidMove(board, column): 196 | animateDroppingToken(board, column, RED) 197 | board[column][getLowestEmptySpace(board, column)] = RED 198 | drawBoard(board) 199 | pygame.display.update() 200 | return 201 | tokenx, tokeny = None, None 202 | draggingToken = False 203 | if tokenx != None and tokeny != None: 204 | drawBoard(board, {'x':tokenx - int(SPACESIZE / 2), 'y':tokeny - int(SPACESIZE / 2), 'color':RED}) 205 | else: 206 | drawBoard(board) 207 | 208 | if isFirstMove: 209 | # Show the help arrow for the player's first move. 210 | DISPLAYSURF.blit(ARROWIMG, ARROWRECT) 211 | 212 | pygame.display.update() 213 | FPSCLOCK.tick() 214 | 215 | 216 | def animateDroppingToken(board, column, color): 217 | x = XMARGIN + column * SPACESIZE 218 | y = YMARGIN - SPACESIZE 219 | dropSpeed = 1.0 220 | 221 | lowestEmptySpace = getLowestEmptySpace(board, column) 222 | 223 | while True: 224 | y += int(dropSpeed) 225 | dropSpeed += 0.5 226 | if int((y - YMARGIN) / SPACESIZE) >= lowestEmptySpace: 227 | return 228 | drawBoard(board, {'x':x, 'y':y, 'color':color}) 229 | pygame.display.update() 230 | FPSCLOCK.tick() 231 | 232 | 233 | def animateComputerMoving(board, column): 234 | x = BLACKPILERECT.left 235 | y = BLACKPILERECT.top 236 | speed = 1.0 237 | # moving the black tile up 238 | while y > (YMARGIN - SPACESIZE): 239 | y -= int(speed) 240 | speed += 0.5 241 | drawBoard(board, {'x':x, 'y':y, 'color':BLACK}) 242 | pygame.display.update() 243 | FPSCLOCK.tick() 244 | # moving the black tile over 245 | y = YMARGIN - SPACESIZE 246 | speed = 1.0 247 | while x > (XMARGIN + column * SPACESIZE): 248 | x -= int(speed) 249 | speed += 0.5 250 | drawBoard(board, {'x':x, 'y':y, 'color':BLACK}) 251 | pygame.display.update() 252 | FPSCLOCK.tick() 253 | # dropping the black tile 254 | animateDroppingToken(board, column, BLACK) 255 | 256 | 257 | def getComputerMove(board): 258 | potentialMoves = getPotentialMoves(board, BLACK, DIFFICULTY) 259 | # get the best fitness from the potential moves 260 | bestMoveFitness = -1 261 | for i in range(BOARDWIDTH): 262 | if potentialMoves[i] > bestMoveFitness and isValidMove(board, i): 263 | bestMoveFitness = potentialMoves[i] 264 | # find all potential moves that have this best fitness 265 | bestMoves = [] 266 | for i in range(len(potentialMoves)): 267 | if potentialMoves[i] == bestMoveFitness and isValidMove(board, i): 268 | bestMoves.append(i) 269 | return random.choice(bestMoves) 270 | 271 | 272 | def getPotentialMoves(board, tile, lookAhead): 273 | if lookAhead == 0 or isBoardFull(board): 274 | return [0] * BOARDWIDTH 275 | 276 | if tile == RED: 277 | enemyTile = BLACK 278 | else: 279 | enemyTile = RED 280 | 281 | # Figure out the best move to make. 282 | potentialMoves = [0] * BOARDWIDTH 283 | for firstMove in range(BOARDWIDTH): 284 | dupeBoard = copy.deepcopy(board) 285 | if not isValidMove(dupeBoard, firstMove): 286 | continue 287 | makeMove(dupeBoard, tile, firstMove) 288 | if isWinner(dupeBoard, tile): 289 | # a winning move automatically gets a perfect fitness 290 | potentialMoves[firstMove] = 1 291 | break # don't bother calculating other moves 292 | else: 293 | # do other player's counter moves and determine best one 294 | if isBoardFull(dupeBoard): 295 | potentialMoves[firstMove] = 0 296 | else: 297 | for counterMove in range(BOARDWIDTH): 298 | dupeBoard2 = copy.deepcopy(dupeBoard) 299 | if not isValidMove(dupeBoard2, counterMove): 300 | continue 301 | makeMove(dupeBoard2, enemyTile, counterMove) 302 | if isWinner(dupeBoard2, enemyTile): 303 | # a losing move automatically gets the worst fitness 304 | potentialMoves[firstMove] = -1 305 | break 306 | else: 307 | # do the recursive call to getPotentialMoves() 308 | results = getPotentialMoves(dupeBoard2, tile, lookAhead - 1) 309 | potentialMoves[firstMove] += (sum(results) / BOARDWIDTH) / BOARDWIDTH 310 | return potentialMoves 311 | 312 | 313 | def getLowestEmptySpace(board, column): 314 | # Return the row number of the lowest empty row in the given column. 315 | for y in range(BOARDHEIGHT-1, -1, -1): 316 | if board[column][y] == EMPTY: 317 | return y 318 | return -1 319 | 320 | 321 | def isValidMove(board, column): 322 | # Returns True if there is an empty space in the given column. 323 | # Otherwise returns False. 324 | if column < 0 or column >= (BOARDWIDTH) or board[column][0] != EMPTY: 325 | return False 326 | return True 327 | 328 | 329 | def isBoardFull(board): 330 | # Returns True if there are no empty spaces anywhere on the board. 331 | for x in range(BOARDWIDTH): 332 | for y in range(BOARDHEIGHT): 333 | if board[x][y] == EMPTY: 334 | return False 335 | return True 336 | 337 | 338 | def isWinner(board, tile): 339 | # check horizontal spaces 340 | for x in range(BOARDWIDTH - 3): 341 | for y in range(BOARDHEIGHT): 342 | if board[x][y] == tile and board[x+1][y] == tile and board[x+2][y] == tile and board[x+3][y] == tile: 343 | return True 344 | # check vertical spaces 345 | for x in range(BOARDWIDTH): 346 | for y in range(BOARDHEIGHT - 3): 347 | if board[x][y] == tile and board[x][y+1] == tile and board[x][y+2] == tile and board[x][y+3] == tile: 348 | return True 349 | # check / diagonal spaces 350 | for x in range(BOARDWIDTH - 3): 351 | for y in range(3, BOARDHEIGHT): 352 | 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: 353 | return True 354 | # check \ diagonal spaces 355 | for x in range(BOARDWIDTH - 3): 356 | for y in range(BOARDHEIGHT - 3): 357 | 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: 358 | return True 359 | return False 360 | 361 | 362 | if __name__ == '__main__': 363 | main() 364 | -------------------------------------------------------------------------------- /squirrel/squirrel.py: -------------------------------------------------------------------------------- 1 | # Squirrel Eat Squirrel (a 2D Katamari Damacy clone) 2 | # By Al Sweigart al@inventwithpython.com 3 | # http://inventwithpython.com/pygame 4 | # Released under a "Simplified BSD" license 5 | 6 | import random, sys, time, math, pygame 7 | from pygame.locals import * 8 | 9 | FPS = 30 # frames per second to update the screen 10 | WINWIDTH = 640 # width of the program's window, in pixels 11 | WINHEIGHT = 480 # height in pixels 12 | HALF_WINWIDTH = int(WINWIDTH / 2) 13 | HALF_WINHEIGHT = int(WINHEIGHT / 2) 14 | 15 | GRASSCOLOR = (24, 255, 0) 16 | WHITE = (255, 255, 255) 17 | RED = (255, 0, 0) 18 | 19 | CAMERASLACK = 90 # how far from the center the squirrel moves before moving the camera 20 | MOVERATE = 9 # how fast the player moves 21 | BOUNCERATE = 6 # how fast the player bounces (large is slower) 22 | BOUNCEHEIGHT = 30 # how high the player bounces 23 | STARTSIZE = 25 # how big the player starts off 24 | WINSIZE = 300 # how big the player needs to be to win 25 | INVULNTIME = 2 # how long the player is invulnerable after being hit in seconds 26 | GAMEOVERTIME = 4 # how long the "game over" text stays on the screen in seconds 27 | MAXHEALTH = 3 # how much health the player starts with 28 | 29 | NUMGRASS = 80 # number of grass objects in the active area 30 | NUMSQUIRRELS = 30 # number of squirrels in the active area 31 | SQUIRRELMINSPEED = 3 # slowest squirrel speed 32 | SQUIRRELMAXSPEED = 7 # fastest squirrel speed 33 | DIRCHANGEFREQ = 2 # % chance of direction change per frame 34 | LEFT = 'left' 35 | RIGHT = 'right' 36 | 37 | """ 38 | This program has three data structures to represent the player, enemy squirrels, and grass background objects. The data structures are dictionaries with the following keys: 39 | 40 | Keys used by all three data structures: 41 | 'x' - the left edge coordinate of the object in the game world (not a pixel coordinate on the screen) 42 | 'y' - the top edge coordinate of the object in the game world (not a pixel coordinate on the screen) 43 | 'rect' - the pygame.Rect object representing where on the screen the object is located. 44 | Player data structure keys: 45 | 'surface' - the pygame.Surface object that stores the image of the squirrel which will be drawn to the screen. 46 | 'facing' - either set to LEFT or RIGHT, stores which direction the player is facing. 47 | 'size' - the width and height of the player in pixels. (The width & height are always the same.) 48 | 'bounce' - represents at what point in a bounce the player is in. 0 means standing (no bounce), up to BOUNCERATE (the completion of the bounce) 49 | 'health' - an integer showing how many more times the player can be hit by a larger squirrel before dying. 50 | Enemy Squirrel data structure keys: 51 | 'surface' - the pygame.Surface object that stores the image of the squirrel which will be drawn to the screen. 52 | 'movex' - how many pixels per frame the squirrel moves horizontally. A negative integer is moving to the left, a positive to the right. 53 | 'movey' - how many pixels per frame the squirrel moves vertically. A negative integer is moving up, a positive moving down. 54 | 'width' - the width of the squirrel's image, in pixels 55 | 'height' - the height of the squirrel's image, in pixels 56 | 'bounce' - represents at what point in a bounce the player is in. 0 means standing (no bounce), up to BOUNCERATE (the completion of the bounce) 57 | 'bouncerate' - how quickly the squirrel bounces. A lower number means a quicker bounce. 58 | 'bounceheight' - how high (in pixels) the squirrel bounces 59 | Grass data structure keys: 60 | 'grassImage' - an integer that refers to the index of the pygame.Surface object in GRASSIMAGES used for this grass object 61 | """ 62 | 63 | def main(): 64 | global FPSCLOCK, DISPLAYSURF, BASICFONT, L_SQUIR_IMG, R_SQUIR_IMG, GRASSIMAGES 65 | 66 | pygame.init() 67 | FPSCLOCK = pygame.time.Clock() 68 | pygame.display.set_icon(pygame.image.load('gameicon.png')) 69 | DISPLAYSURF = pygame.display.set_mode((WINWIDTH, WINHEIGHT)) 70 | pygame.display.set_caption('Squirrel Eat Squirrel') 71 | BASICFONT = pygame.font.Font('freesansbold.ttf', 32) 72 | 73 | # load the image files 74 | L_SQUIR_IMG = pygame.image.load('squirrel.png') 75 | R_SQUIR_IMG = pygame.transform.flip(L_SQUIR_IMG, True, False) 76 | GRASSIMAGES = [] 77 | for i in range(1, 5): 78 | GRASSIMAGES.append(pygame.image.load('grass%s.png' % i)) 79 | 80 | while True: 81 | runGame() 82 | 83 | 84 | def runGame(): 85 | # set up variables for the start of a new game 86 | invulnerableMode = False # if the player is invulnerable 87 | invulnerableStartTime = 0 # time the player became invulnerable 88 | gameOverMode = False # if the player has lost 89 | gameOverStartTime = 0 # time the player lost 90 | winMode = False # if the player has won 91 | 92 | # create the surfaces to hold game text 93 | gameOverSurf = BASICFONT.render('Game Over', True, WHITE) 94 | gameOverRect = gameOverSurf.get_rect() 95 | gameOverRect.center = (HALF_WINWIDTH, HALF_WINHEIGHT) 96 | 97 | winSurf = BASICFONT.render('You have achieved OMEGA SQUIRREL!', True, WHITE) 98 | winRect = winSurf.get_rect() 99 | winRect.center = (HALF_WINWIDTH, HALF_WINHEIGHT) 100 | 101 | winSurf2 = BASICFONT.render('(Press "r" to restart.)', True, WHITE) 102 | winRect2 = winSurf2.get_rect() 103 | winRect2.center = (HALF_WINWIDTH, HALF_WINHEIGHT + 30) 104 | 105 | # camerax and cameray are the top left of where the camera view is 106 | camerax = 0 107 | cameray = 0 108 | 109 | grassObjs = [] # stores all the grass objects in the game 110 | squirrelObjs = [] # stores all the non-player squirrel objects 111 | # stores the player object: 112 | playerObj = {'surface': pygame.transform.scale(L_SQUIR_IMG, (STARTSIZE, STARTSIZE)), 113 | 'facing': LEFT, 114 | 'size': STARTSIZE, 115 | 'x': HALF_WINWIDTH, 116 | 'y': HALF_WINHEIGHT, 117 | 'bounce':0, 118 | 'health': MAXHEALTH} 119 | 120 | moveLeft = False 121 | moveRight = False 122 | moveUp = False 123 | moveDown = False 124 | 125 | # start off with some random grass images on the screen 126 | for i in range(10): 127 | grassObjs.append(makeNewGrass(camerax, cameray)) 128 | grassObjs[i]['x'] = random.randint(0, WINWIDTH) 129 | grassObjs[i]['y'] = random.randint(0, WINHEIGHT) 130 | 131 | while True: # main game loop 132 | # Check if we should turn off invulnerability 133 | if invulnerableMode and time.time() - invulnerableStartTime > INVULNTIME: 134 | invulnerableMode = False 135 | 136 | # move all the squirrels 137 | for sObj in squirrelObjs: 138 | # move the squirrel, and adjust for their bounce 139 | sObj['x'] += sObj['movex'] 140 | sObj['y'] += sObj['movey'] 141 | sObj['bounce'] += 1 142 | if sObj['bounce'] > sObj['bouncerate']: 143 | sObj['bounce'] = 0 # reset bounce amount 144 | 145 | # random chance they change direction 146 | if random.randint(0, 99) < DIRCHANGEFREQ: 147 | sObj['movex'] = getRandomVelocity() 148 | sObj['movey'] = getRandomVelocity() 149 | if sObj['movex'] > 0: # faces right 150 | sObj['surface'] = pygame.transform.scale(R_SQUIR_IMG, (sObj['width'], sObj['height'])) 151 | else: # faces left 152 | sObj['surface'] = pygame.transform.scale(L_SQUIR_IMG, (sObj['width'], sObj['height'])) 153 | 154 | 155 | # go through all the objects and see if any need to be deleted. 156 | for i in range(len(grassObjs) - 1, -1, -1): 157 | if isOutsideActiveArea(camerax, cameray, grassObjs[i]): 158 | del grassObjs[i] 159 | for i in range(len(squirrelObjs) - 1, -1, -1): 160 | if isOutsideActiveArea(camerax, cameray, squirrelObjs[i]): 161 | del squirrelObjs[i] 162 | 163 | # add more grass & squirrels if we don't have enough. 164 | while len(grassObjs) < NUMGRASS: 165 | grassObjs.append(makeNewGrass(camerax, cameray)) 166 | while len(squirrelObjs) < NUMSQUIRRELS: 167 | squirrelObjs.append(makeNewSquirrel(camerax, cameray)) 168 | 169 | # adjust camerax and cameray if beyond the "camera slack" 170 | playerCenterx = playerObj['x'] + int(playerObj['size'] / 2) 171 | playerCentery = playerObj['y'] + int(playerObj['size'] / 2) 172 | if (camerax + HALF_WINWIDTH) - playerCenterx > CAMERASLACK: 173 | camerax = playerCenterx + CAMERASLACK - HALF_WINWIDTH 174 | elif playerCenterx - (camerax + HALF_WINWIDTH) > CAMERASLACK: 175 | camerax = playerCenterx - CAMERASLACK - HALF_WINWIDTH 176 | if (cameray + HALF_WINHEIGHT) - playerCentery > CAMERASLACK: 177 | cameray = playerCentery + CAMERASLACK - HALF_WINHEIGHT 178 | elif playerCentery - (cameray + HALF_WINHEIGHT) > CAMERASLACK: 179 | cameray = playerCentery - CAMERASLACK - HALF_WINHEIGHT 180 | 181 | # draw the green background 182 | DISPLAYSURF.fill(GRASSCOLOR) 183 | 184 | # draw all the grass objects on the screen 185 | for gObj in grassObjs: 186 | gRect = pygame.Rect( (gObj['x'] - camerax, 187 | gObj['y'] - cameray, 188 | gObj['width'], 189 | gObj['height']) ) 190 | DISPLAYSURF.blit(GRASSIMAGES[gObj['grassImage']], gRect) 191 | 192 | 193 | # draw the other squirrels 194 | for sObj in squirrelObjs: 195 | sObj['rect'] = pygame.Rect( (sObj['x'] - camerax, 196 | sObj['y'] - cameray - getBounceAmount(sObj['bounce'], sObj['bouncerate'], sObj['bounceheight']), 197 | sObj['width'], 198 | sObj['height']) ) 199 | DISPLAYSURF.blit(sObj['surface'], sObj['rect']) 200 | 201 | 202 | # draw the player squirrel 203 | flashIsOn = round(time.time(), 1) * 10 % 2 == 1 204 | if not gameOverMode and not (invulnerableMode and flashIsOn): 205 | playerObj['rect'] = pygame.Rect( (playerObj['x'] - camerax, 206 | playerObj['y'] - cameray - getBounceAmount(playerObj['bounce'], BOUNCERATE, BOUNCEHEIGHT), 207 | playerObj['size'], 208 | playerObj['size']) ) 209 | DISPLAYSURF.blit(playerObj['surface'], playerObj['rect']) 210 | 211 | 212 | # draw the health meter 213 | drawHealthMeter(playerObj['health']) 214 | 215 | for event in pygame.event.get(): # event handling loop 216 | if event.type == QUIT: 217 | terminate() 218 | 219 | elif event.type == KEYDOWN: 220 | if event.key in (K_UP, K_w): 221 | moveDown = False 222 | moveUp = True 223 | elif event.key in (K_DOWN, K_s): 224 | moveUp = False 225 | moveDown = True 226 | elif event.key in (K_LEFT, K_a): 227 | moveRight = False 228 | moveLeft = True 229 | if playerObj['facing'] != LEFT: # change player image 230 | playerObj['surface'] = pygame.transform.scale(L_SQUIR_IMG, (playerObj['size'], playerObj['size'])) 231 | playerObj['facing'] = LEFT 232 | elif event.key in (K_RIGHT, K_d): 233 | moveLeft = False 234 | moveRight = True 235 | if playerObj['facing'] != RIGHT: # change player image 236 | playerObj['surface'] = pygame.transform.scale(R_SQUIR_IMG, (playerObj['size'], playerObj['size'])) 237 | playerObj['facing'] = RIGHT 238 | elif winMode and event.key == K_r: 239 | return 240 | 241 | elif event.type == KEYUP: 242 | # stop moving the player's squirrel 243 | if event.key in (K_LEFT, K_a): 244 | moveLeft = False 245 | elif event.key in (K_RIGHT, K_d): 246 | moveRight = False 247 | elif event.key in (K_UP, K_w): 248 | moveUp = False 249 | elif event.key in (K_DOWN, K_s): 250 | moveDown = False 251 | 252 | elif event.key == K_ESCAPE: 253 | terminate() 254 | 255 | if not gameOverMode: 256 | # actually move the player 257 | if moveLeft: 258 | playerObj['x'] -= MOVERATE 259 | if moveRight: 260 | playerObj['x'] += MOVERATE 261 | if moveUp: 262 | playerObj['y'] -= MOVERATE 263 | if moveDown: 264 | playerObj['y'] += MOVERATE 265 | 266 | if (moveLeft or moveRight or moveUp or moveDown) or playerObj['bounce'] != 0: 267 | playerObj['bounce'] += 1 268 | 269 | if playerObj['bounce'] > BOUNCERATE: 270 | playerObj['bounce'] = 0 # reset bounce amount 271 | 272 | # check if the player has collided with any squirrels 273 | for i in range(len(squirrelObjs)-1, -1, -1): 274 | sqObj = squirrelObjs[i] 275 | if 'rect' in sqObj and playerObj['rect'].colliderect(sqObj['rect']): 276 | # a player/squirrel collision has occurred 277 | 278 | if sqObj['width'] * sqObj['height'] <= playerObj['size']**2: 279 | # player is larger and eats the squirrel 280 | playerObj['size'] += int( (sqObj['width'] * sqObj['height'])**0.2 ) + 1 281 | del squirrelObjs[i] 282 | 283 | if playerObj['facing'] == LEFT: 284 | playerObj['surface'] = pygame.transform.scale(L_SQUIR_IMG, (playerObj['size'], playerObj['size'])) 285 | if playerObj['facing'] == RIGHT: 286 | playerObj['surface'] = pygame.transform.scale(R_SQUIR_IMG, (playerObj['size'], playerObj['size'])) 287 | 288 | if playerObj['size'] > WINSIZE: 289 | winMode = True # turn on "win mode" 290 | 291 | elif not invulnerableMode: 292 | # player is smaller and takes damage 293 | invulnerableMode = True 294 | invulnerableStartTime = time.time() 295 | playerObj['health'] -= 1 296 | if playerObj['health'] == 0: 297 | gameOverMode = True # turn on "game over mode" 298 | gameOverStartTime = time.time() 299 | else: 300 | # game is over, show "game over" text 301 | DISPLAYSURF.blit(gameOverSurf, gameOverRect) 302 | if time.time() - gameOverStartTime > GAMEOVERTIME: 303 | return # end the current game 304 | 305 | # check if the player has won. 306 | if winMode: 307 | DISPLAYSURF.blit(winSurf, winRect) 308 | DISPLAYSURF.blit(winSurf2, winRect2) 309 | 310 | pygame.display.update() 311 | FPSCLOCK.tick(FPS) 312 | 313 | 314 | 315 | 316 | def drawHealthMeter(currentHealth): 317 | for i in range(currentHealth): # draw red health bars 318 | pygame.draw.rect(DISPLAYSURF, RED, (15, 5 + (10 * MAXHEALTH) - i * 10, 20, 10)) 319 | for i in range(MAXHEALTH): # draw the white outlines 320 | pygame.draw.rect(DISPLAYSURF, WHITE, (15, 5 + (10 * MAXHEALTH) - i * 10, 20, 10), 1) 321 | 322 | 323 | def terminate(): 324 | pygame.quit() 325 | sys.exit() 326 | 327 | 328 | def getBounceAmount(currentBounce, bounceRate, bounceHeight): 329 | # Returns the number of pixels to offset based on the bounce. 330 | # Larger bounceRate means a slower bounce. 331 | # Larger bounceHeight means a higher bounce. 332 | # currentBounce will always be less than bounceRate 333 | return int(math.sin( (math.pi / float(bounceRate)) * currentBounce ) * bounceHeight) 334 | 335 | def getRandomVelocity(): 336 | speed = random.randint(SQUIRRELMINSPEED, SQUIRRELMAXSPEED) 337 | if random.randint(0, 1) == 0: 338 | return speed 339 | else: 340 | return -speed 341 | 342 | 343 | def getRandomOffCameraPos(camerax, cameray, objWidth, objHeight): 344 | # create a Rect of the camera view 345 | cameraRect = pygame.Rect(camerax, cameray, WINWIDTH, WINHEIGHT) 346 | while True: 347 | x = random.randint(camerax - WINWIDTH, camerax + (2 * WINWIDTH)) 348 | y = random.randint(cameray - WINHEIGHT, cameray + (2 * WINHEIGHT)) 349 | # create a Rect object with the random coordinates and use colliderect() 350 | # to make sure the right edge isn't in the camera view. 351 | objRect = pygame.Rect(x, y, objWidth, objHeight) 352 | if not objRect.colliderect(cameraRect): 353 | return x, y 354 | 355 | 356 | def makeNewSquirrel(camerax, cameray): 357 | sq = {} 358 | generalSize = random.randint(5, 25) 359 | multiplier = random.randint(1, 3) 360 | sq['width'] = (generalSize + random.randint(0, 10)) * multiplier 361 | sq['height'] = (generalSize + random.randint(0, 10)) * multiplier 362 | sq['x'], sq['y'] = getRandomOffCameraPos(camerax, cameray, sq['width'], sq['height']) 363 | sq['movex'] = getRandomVelocity() 364 | sq['movey'] = getRandomVelocity() 365 | if sq['movex'] < 0: # squirrel is facing left 366 | sq['surface'] = pygame.transform.scale(L_SQUIR_IMG, (sq['width'], sq['height'])) 367 | else: # squirrel is facing right 368 | sq['surface'] = pygame.transform.scale(R_SQUIR_IMG, (sq['width'], sq['height'])) 369 | sq['bounce'] = 0 370 | sq['bouncerate'] = random.randint(10, 18) 371 | sq['bounceheight'] = random.randint(10, 50) 372 | return sq 373 | 374 | 375 | def makeNewGrass(camerax, cameray): 376 | gr = {} 377 | gr['grassImage'] = random.randint(0, len(GRASSIMAGES) - 1) 378 | gr['width'] = GRASSIMAGES[0].get_width() 379 | gr['height'] = GRASSIMAGES[0].get_height() 380 | gr['x'], gr['y'] = getRandomOffCameraPos(camerax, cameray, gr['width'], gr['height']) 381 | gr['rect'] = pygame.Rect( (gr['x'], gr['y'], gr['width'], gr['height']) ) 382 | return gr 383 | 384 | 385 | def isOutsideActiveArea(camerax, cameray, obj): 386 | # Return False if camerax and cameray are more than 387 | # a half-window length beyond the edge of the window. 388 | boundsLeftEdge = camerax - WINWIDTH 389 | boundsTopEdge = cameray - WINHEIGHT 390 | boundsRect = pygame.Rect(boundsLeftEdge, boundsTopEdge, WINWIDTH * 3, WINHEIGHT * 3) 391 | objRect = pygame.Rect(obj['x'], obj['y'], obj['width'], obj['height']) 392 | return not boundsRect.colliderect(objRect) 393 | 394 | 395 | if __name__ == '__main__': 396 | main() -------------------------------------------------------------------------------- /inkspill/inkspill.py: -------------------------------------------------------------------------------- 1 | # Ink Spill (a Flood It clone) 2 | # http://inventwithpython.com/pygame 3 | # By Al Sweigart al@inventwithpython.com 4 | # Released under a "Simplified BSD" license 5 | 6 | import random, sys, webbrowser, copy, pygame 7 | from pygame.locals import * 8 | 9 | # There are different box sizes, number of boxes, and 10 | # life depending on the "board size" setting selected. 11 | SMALLBOXSIZE = 60 # size is in pixels 12 | MEDIUMBOXSIZE = 20 13 | LARGEBOXSIZE = 11 14 | 15 | SMALLBOARDSIZE = 6 # size is in boxes 16 | MEDIUMBOARDSIZE = 17 17 | LARGEBOARDSIZE = 30 18 | 19 | SMALLMAXLIFE = 10 # number of turns 20 | MEDIUMMAXLIFE = 30 21 | LARGEMAXLIFE = 64 22 | 23 | FPS = 30 24 | WINDOWWIDTH = 640 25 | WINDOWHEIGHT = 480 26 | boxSize = MEDIUMBOXSIZE 27 | PALETTEGAPSIZE = 10 28 | PALETTESIZE = 45 29 | EASY = 0 # arbitrary but unique value 30 | MEDIUM = 1 # arbitrary but unique value 31 | HARD = 2 # arbitrary but unique value 32 | 33 | difficulty = MEDIUM # game starts in "medium" mode 34 | maxLife = MEDIUMMAXLIFE 35 | boardWidth = MEDIUMBOARDSIZE 36 | boardHeight = MEDIUMBOARDSIZE 37 | 38 | 39 | # R G B 40 | WHITE = (255, 255, 255) 41 | DARKGRAY = ( 70, 70, 70) 42 | BLACK = ( 0, 0, 0) 43 | RED = (255, 0, 0) 44 | GREEN = ( 0, 255, 0) 45 | BLUE = ( 0, 0, 255) 46 | YELLOW = (255, 255, 0) 47 | ORANGE = (255, 128, 0) 48 | PURPLE = (255, 0, 255) 49 | 50 | # The first color in each scheme is the background color, the next six are the palette colors. 51 | COLORSCHEMES = (((150, 200, 255), RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE), 52 | ((0, 155, 104), (97, 215, 164), (228, 0, 69), (0, 125, 50), (204, 246, 0), (148, 0, 45), (241, 109, 149)), 53 | ((195, 179, 0), (255, 239, 115), (255, 226, 0), (147, 3, 167), (24, 38, 176), (166, 147, 0), (197, 97, 211)), 54 | ((85, 0, 0), (155, 39, 102), (0, 201, 13), (255, 118, 0), (206, 0, 113), (0, 130, 9), (255, 180, 115)), 55 | ((191, 159, 64), (183, 182, 208), (4, 31, 183), (167, 184, 45), (122, 128, 212), (37, 204, 7), (88, 155, 213)), 56 | ((200, 33, 205), (116, 252, 185), (68, 56, 56), (52, 238, 83), (23, 149, 195), (222, 157, 227), (212, 86, 185))) 57 | for i in range(len(COLORSCHEMES)): 58 | assert len(COLORSCHEMES[i]) == 7, 'Color scheme %s does not have exactly 7 colors.' % (i) 59 | bgColor = COLORSCHEMES[0][0] 60 | paletteColors = COLORSCHEMES[0][1:] 61 | 62 | def main(): 63 | global FPSCLOCK, DISPLAYSURF, LOGOIMAGE, SPOTIMAGE, SETTINGSIMAGE, SETTINGSBUTTONIMAGE, RESETBUTTONIMAGE 64 | 65 | pygame.init() 66 | FPSCLOCK = pygame.time.Clock() 67 | DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) 68 | 69 | # Load images 70 | LOGOIMAGE = pygame.image.load('inkspilllogo.png') 71 | SPOTIMAGE = pygame.image.load('inkspillspot.png') 72 | SETTINGSIMAGE = pygame.image.load('inkspillsettings.png') 73 | SETTINGSBUTTONIMAGE = pygame.image.load('inkspillsettingsbutton.png') 74 | RESETBUTTONIMAGE = pygame.image.load('inkspillresetbutton.png') 75 | 76 | pygame.display.set_caption('Ink Spill') 77 | mousex = 0 78 | mousey = 0 79 | mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty) 80 | life = maxLife 81 | lastPaletteClicked = None 82 | 83 | while True: # main game loop 84 | paletteClicked = None 85 | resetGame = False 86 | 87 | # Draw the screen. 88 | DISPLAYSURF.fill(bgColor) 89 | drawLogoAndButtons() 90 | drawBoard(mainBoard) 91 | drawLifeMeter(life) 92 | drawPalettes() 93 | 94 | checkForQuit() 95 | for event in pygame.event.get(): # event handling loop 96 | if event.type == MOUSEBUTTONUP: 97 | mousex, mousey = event.pos 98 | if pygame.Rect(WINDOWWIDTH - SETTINGSBUTTONIMAGE.get_width(), 99 | WINDOWHEIGHT - SETTINGSBUTTONIMAGE.get_height(), 100 | SETTINGSBUTTONIMAGE.get_width(), 101 | SETTINGSBUTTONIMAGE.get_height()).collidepoint(mousex, mousey): 102 | resetGame = showSettingsScreen() # clicked on Settings button 103 | elif pygame.Rect(WINDOWWIDTH - RESETBUTTONIMAGE.get_width(), 104 | WINDOWHEIGHT - SETTINGSBUTTONIMAGE.get_height() - RESETBUTTONIMAGE.get_height(), 105 | RESETBUTTONIMAGE.get_width(), 106 | RESETBUTTONIMAGE.get_height()).collidepoint(mousex, mousey): 107 | resetGame = True # clicked on Reset button 108 | else: 109 | # check if a palette button was clicked 110 | paletteClicked = getColorOfPaletteAt(mousex, mousey) 111 | 112 | if paletteClicked != None and paletteClicked != lastPaletteClicked: 113 | # a palette button was clicked that is different from the 114 | # last palette button clicked (this check prevents the player 115 | # from accidentally clicking the same palette twice) 116 | lastPaletteClicked = paletteClicked 117 | floodAnimation(mainBoard, paletteClicked) 118 | life -= 1 119 | 120 | resetGame = False 121 | if hasWon(mainBoard): 122 | for i in range(4): # flash border 4 times 123 | flashBorderAnimation(WHITE, mainBoard) 124 | resetGame = True 125 | pygame.time.wait(2000) # pause so the player can bask in victory 126 | elif life == 0: 127 | # life is zero, so player has lost 128 | drawLifeMeter(0) 129 | pygame.display.update() 130 | pygame.time.wait(400) 131 | for i in range(4): 132 | flashBorderAnimation(BLACK, mainBoard) 133 | resetGame = True 134 | pygame.time.wait(2000) # pause so the player can suffer in their defeat 135 | 136 | if resetGame: 137 | # start a new game 138 | mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty) 139 | life = maxLife 140 | lastPaletteClicked = None 141 | 142 | pygame.display.update() 143 | FPSCLOCK.tick(FPS) 144 | 145 | 146 | def checkForQuit(): 147 | # Terminates the program if there are any QUIT or escape key events. 148 | for event in pygame.event.get(QUIT): # get all the QUIT events 149 | pygame.quit() # terminate if any QUIT events are present 150 | sys.exit() 151 | for event in pygame.event.get(KEYUP): # get all the KEYUP events 152 | if event.key == K_ESCAPE: 153 | pygame.quit() # terminate if the KEYUP event was for the Esc key 154 | sys.exit() 155 | pygame.event.post(event) # put the other KEYUP event objects back 156 | 157 | 158 | def hasWon(board): 159 | # if the entire board is the same color, player has won 160 | for x in range(boardWidth): 161 | for y in range(boardHeight): 162 | if board[x][y] != board[0][0]: 163 | return False # found a different color, player has not won 164 | return True 165 | 166 | 167 | def showSettingsScreen(): 168 | global difficulty, boxSize, boardWidth, boardHeight, maxLife, paletteColors, bgColor 169 | 170 | # The pixel coordinates in this function were obtained by loading 171 | # the inkspillsettings.png image into a graphics editor and reading 172 | # the pixel coordinates from there. Handy trick. 173 | 174 | origDifficulty = difficulty 175 | origBoxSize = boxSize 176 | screenNeedsRedraw = True 177 | 178 | while True: 179 | if screenNeedsRedraw: 180 | DISPLAYSURF.fill(bgColor) 181 | DISPLAYSURF.blit(SETTINGSIMAGE, (0,0)) 182 | 183 | # place the ink spot marker next to the selected difficulty 184 | if difficulty == EASY: 185 | DISPLAYSURF.blit(SPOTIMAGE, (30, 4)) 186 | if difficulty == MEDIUM: 187 | DISPLAYSURF.blit(SPOTIMAGE, (8, 41)) 188 | if difficulty == HARD: 189 | DISPLAYSURF.blit(SPOTIMAGE, (30, 76)) 190 | 191 | # place the ink spot marker next to the selected size 192 | if boxSize == SMALLBOXSIZE: 193 | DISPLAYSURF.blit(SPOTIMAGE, (22, 150)) 194 | if boxSize == MEDIUMBOXSIZE: 195 | DISPLAYSURF.blit(SPOTIMAGE, (11, 185)) 196 | if boxSize == LARGEBOXSIZE: 197 | DISPLAYSURF.blit(SPOTIMAGE, (24, 220)) 198 | 199 | for i in range(len(COLORSCHEMES)): 200 | drawColorSchemeBoxes(500, i * 60 + 30, i) 201 | 202 | pygame.display.update() 203 | 204 | screenNeedsRedraw = False # by default, don't redraw the screen 205 | for event in pygame.event.get(): # event handling loop 206 | if event.type == QUIT: 207 | pygame.quit() 208 | sys.exit() 209 | elif event.type == KEYUP: 210 | if event.key == K_ESCAPE: 211 | # Esc key on settings screen goes back to game 212 | return not (origDifficulty == difficulty and origBoxSize == boxSize) 213 | elif event.type == MOUSEBUTTONUP: 214 | screenNeedsRedraw = True # screen should be redrawn 215 | mousex, mousey = event.pos # syntactic sugar 216 | 217 | # check for clicks on the difficulty buttons 218 | if pygame.Rect(74, 16, 111, 30).collidepoint(mousex, mousey): 219 | difficulty = EASY 220 | elif pygame.Rect(53, 50, 104, 29).collidepoint(mousex, mousey): 221 | difficulty = MEDIUM 222 | elif pygame.Rect(72, 85, 65, 31).collidepoint(mousex, mousey): 223 | difficulty = HARD 224 | 225 | # check for clicks on the size buttons 226 | elif pygame.Rect(63, 156, 84, 31).collidepoint(mousex, mousey): 227 | # small board size setting: 228 | boxSize = SMALLBOXSIZE 229 | boardWidth = SMALLBOARDSIZE 230 | boardHeight = SMALLBOARDSIZE 231 | maxLife = SMALLMAXLIFE 232 | elif pygame.Rect(52, 192, 106,32).collidepoint(mousex, mousey): 233 | # medium board size setting: 234 | boxSize = MEDIUMBOXSIZE 235 | boardWidth = MEDIUMBOARDSIZE 236 | boardHeight = MEDIUMBOARDSIZE 237 | maxLife = MEDIUMMAXLIFE 238 | elif pygame.Rect(67, 228, 58, 37).collidepoint(mousex, mousey): 239 | # large board size setting: 240 | boxSize = LARGEBOXSIZE 241 | boardWidth = LARGEBOARDSIZE 242 | boardHeight = LARGEBOARDSIZE 243 | maxLife = LARGEMAXLIFE 244 | elif pygame.Rect(14, 299, 371, 97).collidepoint(mousex, mousey): 245 | # clicked on the "learn programming" ad 246 | webbrowser.open('http://inventwithpython.com') # opens a web browser 247 | elif pygame.Rect(178, 418, 215, 34).collidepoint(mousex, mousey): 248 | # clicked on the "back to game" button 249 | return not (origDifficulty == difficulty and origBoxSize == boxSize) 250 | 251 | for i in range(len(COLORSCHEMES)): 252 | # clicked on a color scheme button 253 | if pygame.Rect(500, 30 + i * 60, MEDIUMBOXSIZE * 3, MEDIUMBOXSIZE * 2).collidepoint(mousex, mousey): 254 | bgColor = COLORSCHEMES[i][0] 255 | paletteColors = COLORSCHEMES[i][1:] 256 | 257 | 258 | def drawColorSchemeBoxes(x, y, schemeNum): 259 | # Draws the color scheme boxes that appear on the "Settings" screen. 260 | for boxy in range(2): 261 | for boxx in range(3): 262 | pygame.draw.rect(DISPLAYSURF, COLORSCHEMES[schemeNum][3 * boxy + boxx + 1], (x + MEDIUMBOXSIZE * boxx, y + MEDIUMBOXSIZE * boxy, MEDIUMBOXSIZE, MEDIUMBOXSIZE)) 263 | if paletteColors == COLORSCHEMES[schemeNum][1:]: 264 | # put the ink spot next to the selected color scheme 265 | DISPLAYSURF.blit(SPOTIMAGE, (x - 50, y)) 266 | 267 | 268 | def flashBorderAnimation(color, board, animationSpeed=30): 269 | origSurf = DISPLAYSURF.copy() 270 | flashSurf = pygame.Surface(DISPLAYSURF.get_size()) 271 | flashSurf = flashSurf.convert_alpha() 272 | for start, end, step in ((0, 256, 1), (255, 0, -1)): 273 | # the first iteration on the outer loop will set the inner loop 274 | # to have transparency go from 0 to 255, the second iteration will 275 | # have it go from 255 to 0. This is the "flash". 276 | for transparency in range(start, end, animationSpeed * step): 277 | DISPLAYSURF.blit(origSurf, (0, 0)) 278 | r, g, b = color 279 | flashSurf.fill((r, g, b, transparency)) 280 | DISPLAYSURF.blit(flashSurf, (0, 0)) 281 | drawBoard(board) # draw board ON TOP OF the transparency layer 282 | pygame.display.update() 283 | FPSCLOCK.tick(FPS) 284 | DISPLAYSURF.blit(origSurf, (0, 0)) # redraw the original surface 285 | 286 | 287 | def floodAnimation(board, paletteClicked, animationSpeed=25): 288 | origBoard = copy.deepcopy(board) 289 | floodFill(board, board[0][0], paletteClicked, 0, 0) 290 | 291 | for transparency in range(0, 255, animationSpeed): 292 | # The "new" board slowly become opaque over the original board. 293 | drawBoard(origBoard) 294 | drawBoard(board, transparency) 295 | pygame.display.update() 296 | FPSCLOCK.tick(FPS) 297 | 298 | 299 | def generateRandomBoard(width, height, difficulty=MEDIUM): 300 | # Creates a board data structure with random colors for each box. 301 | board = [] 302 | for x in range(width): 303 | column = [] 304 | for y in range(height): 305 | column.append(random.randint(0, len(paletteColors) - 1)) 306 | board.append(column) 307 | 308 | # Make board easier by setting some boxes to same color as a neighbor. 309 | 310 | # Determine how many boxes to change. 311 | if difficulty == EASY: 312 | if boxSize == SMALLBOXSIZE: 313 | boxesToChange = 100 314 | else: 315 | boxesToChange = 1500 316 | elif difficulty == MEDIUM: 317 | if boxSize == SMALLBOXSIZE: 318 | boxesToChange = 5 319 | else: 320 | boxesToChange = 200 321 | else: 322 | boxesToChange = 0 323 | 324 | # Change neighbor's colors: 325 | for i in range(boxesToChange): 326 | # Randomly choose a box whose color to copy 327 | x = random.randint(1, width-2) 328 | y = random.randint(1, height-2) 329 | 330 | # Randomly choose neighbors to change. 331 | direction = random.randint(0, 3) 332 | if direction == 0: # change left and up neighbor 333 | board[x-1][y] == board[x][y] 334 | board[x][y-1] == board[x][y] 335 | elif direction == 1: # change right and down neighbor 336 | board[x+1][y] == board[x][y] 337 | board[x][y+1] == board[x][y] 338 | elif direction == 2: # change right and up neighbor 339 | board[x][y-1] == board[x][y] 340 | board[x+1][y] == board[x][y] 341 | else: # change left and down neighbor 342 | board[x][y+1] == board[x][y] 343 | board[x-1][y] == board[x][y] 344 | return board 345 | 346 | 347 | def drawLogoAndButtons(): 348 | # draw the Ink Spill logo and Settings and Reset buttons. 349 | DISPLAYSURF.blit(LOGOIMAGE, (WINDOWWIDTH - LOGOIMAGE.get_width(), 0)) 350 | DISPLAYSURF.blit(SETTINGSBUTTONIMAGE, (WINDOWWIDTH - SETTINGSBUTTONIMAGE.get_width(), WINDOWHEIGHT - SETTINGSBUTTONIMAGE.get_height())) 351 | DISPLAYSURF.blit(RESETBUTTONIMAGE, (WINDOWWIDTH - RESETBUTTONIMAGE.get_width(), WINDOWHEIGHT - SETTINGSBUTTONIMAGE.get_height() - RESETBUTTONIMAGE.get_height())) 352 | 353 | 354 | def drawBoard(board, transparency=255): 355 | # The colored squares are drawn to a temporary surface which is then 356 | # drawn to the DISPLAYSURF surface. This is done so we can draw the 357 | # squares with transparency on top of DISPLAYSURF as it currently is. 358 | tempSurf = pygame.Surface(DISPLAYSURF.get_size()) 359 | tempSurf = tempSurf.convert_alpha() 360 | tempSurf.fill((0, 0, 0, 0)) 361 | 362 | for x in range(boardWidth): 363 | for y in range(boardHeight): 364 | left, top = leftTopPixelCoordOfBox(x, y) 365 | r, g, b = paletteColors[board[x][y]] 366 | pygame.draw.rect(tempSurf, (r, g, b, transparency), (left, top, boxSize, boxSize)) 367 | left, top = leftTopPixelCoordOfBox(0, 0) 368 | pygame.draw.rect(tempSurf, BLACK, (left-1, top-1, boxSize * boardWidth + 1, boxSize * boardHeight + 1), 1) 369 | DISPLAYSURF.blit(tempSurf, (0, 0)) 370 | 371 | 372 | def drawPalettes(): 373 | # Draws the six color palettes at the bottom of the screen. 374 | numColors = len(paletteColors) 375 | xmargin = int((WINDOWWIDTH - ((PALETTESIZE * numColors) + (PALETTEGAPSIZE * (numColors - 1)))) / 2) 376 | for i in range(numColors): 377 | left = xmargin + (i * PALETTESIZE) + (i * PALETTEGAPSIZE) 378 | top = WINDOWHEIGHT - PALETTESIZE - 10 379 | pygame.draw.rect(DISPLAYSURF, paletteColors[i], (left, top, PALETTESIZE, PALETTESIZE)) 380 | pygame.draw.rect(DISPLAYSURF, bgColor, (left + 2, top + 2, PALETTESIZE - 4, PALETTESIZE - 4), 2) 381 | 382 | 383 | def drawLifeMeter(currentLife): 384 | lifeBoxSize = int((WINDOWHEIGHT - 40) / maxLife) 385 | 386 | # Draw background color of life meter. 387 | pygame.draw.rect(DISPLAYSURF, bgColor, (20, 20, 20, 20 + (maxLife * lifeBoxSize))) 388 | 389 | for i in range(maxLife): 390 | if currentLife >= (maxLife - i): # draw a solid red box 391 | pygame.draw.rect(DISPLAYSURF, RED, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize)) 392 | pygame.draw.rect(DISPLAYSURF, WHITE, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize), 1) # draw white outline 393 | 394 | 395 | def getColorOfPaletteAt(x, y): 396 | # Returns the index of the color in paletteColors that the x and y parameters 397 | # are over. Returns None if x and y are not over any palette. 398 | numColors = len(paletteColors) 399 | xmargin = int((WINDOWWIDTH - ((PALETTESIZE * numColors) + (PALETTEGAPSIZE * (numColors - 1)))) / 2) 400 | top = WINDOWHEIGHT - PALETTESIZE - 10 401 | for i in range(numColors): 402 | # Find out if the mouse click is inside any of the palettes. 403 | left = xmargin + (i * PALETTESIZE) + (i * PALETTEGAPSIZE) 404 | r = pygame.Rect(left, top, PALETTESIZE, PALETTESIZE) 405 | if r.collidepoint(x, y): 406 | return i 407 | return None # no palette exists at these x, y coordinates 408 | 409 | 410 | def floodFill(board, oldColor, newColor, x, y): 411 | # This is the flood fill algorithm. 412 | if oldColor == newColor or board[x][y] != oldColor: 413 | return 414 | 415 | board[x][y] = newColor # change the color of the current box 416 | 417 | # Make the recursive call for any neighboring boxes: 418 | if x > 0: 419 | floodFill(board, oldColor, newColor, x - 1, y) # on box to the left 420 | if x < boardWidth - 1: 421 | floodFill(board, oldColor, newColor, x + 1, y) # on box to the right 422 | if y > 0: 423 | floodFill(board, oldColor, newColor, x, y - 1) # on box to up 424 | if y < boardHeight - 1: 425 | floodFill(board, oldColor, newColor, x, y + 1) # on box to down 426 | 427 | 428 | def leftTopPixelCoordOfBox(boxx, boxy): 429 | # Returns the x and y of the left-topmost pixel of the xth & yth box. 430 | xmargin = int((WINDOWWIDTH - (boardWidth * boxSize)) / 2) 431 | ymargin = int((WINDOWHEIGHT - (boardHeight * boxSize)) / 2) 432 | return (boxx * boxSize + xmargin, boxy * boxSize + ymargin) 433 | 434 | 435 | if __name__ == '__main__': 436 | main() 437 | -------------------------------------------------------------------------------- /tetromino/tetromino.py: -------------------------------------------------------------------------------- 1 | # Tetromino (a Tetris clone) 2 | # By Al Sweigart al@inventwithpython.com 3 | # http://inventwithpython.com/pygame 4 | # Released under a "Simplified BSD" license 5 | 6 | import random, time, pygame, sys 7 | from pygame.locals import * 8 | 9 | FPS = 25 10 | WINDOWWIDTH = 640 11 | WINDOWHEIGHT = 480 12 | BOXSIZE = 20 13 | BOARDWIDTH = 10 14 | BOARDHEIGHT = 20 15 | BLANK = '.' 16 | 17 | MOVESIDEWAYSFREQ = 0.15 18 | MOVEDOWNFREQ = 0.1 19 | 20 | XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * BOXSIZE) / 2) 21 | TOPMARGIN = WINDOWHEIGHT - (BOARDHEIGHT * BOXSIZE) - 5 22 | 23 | # R G B 24 | WHITE = (255, 255, 255) 25 | GRAY = (185, 185, 185) 26 | BLACK = ( 0, 0, 0) 27 | RED = (155, 0, 0) 28 | LIGHTRED = (175, 20, 20) 29 | GREEN = ( 0, 155, 0) 30 | LIGHTGREEN = ( 20, 175, 20) 31 | BLUE = ( 0, 0, 155) 32 | LIGHTBLUE = ( 20, 20, 175) 33 | YELLOW = (155, 155, 0) 34 | LIGHTYELLOW = (175, 175, 20) 35 | 36 | BORDERCOLOR = BLUE 37 | BGCOLOR = BLACK 38 | TEXTCOLOR = WHITE 39 | TEXTSHADOWCOLOR = GRAY 40 | COLORS = ( BLUE, GREEN, RED, YELLOW) 41 | LIGHTCOLORS = (LIGHTBLUE, LIGHTGREEN, LIGHTRED, LIGHTYELLOW) 42 | assert len(COLORS) == len(LIGHTCOLORS) # each color must have light color 43 | 44 | TEMPLATEWIDTH = 5 45 | TEMPLATEHEIGHT = 5 46 | 47 | S_SHAPE_TEMPLATE = [['.....', 48 | '.....', 49 | '..OO.', 50 | '.OO..', 51 | '.....'], 52 | ['.....', 53 | '..O..', 54 | '..OO.', 55 | '...O.', 56 | '.....']] 57 | 58 | Z_SHAPE_TEMPLATE = [['.....', 59 | '.....', 60 | '.OO..', 61 | '..OO.', 62 | '.....'], 63 | ['.....', 64 | '..O..', 65 | '.OO..', 66 | '.O...', 67 | '.....']] 68 | 69 | I_SHAPE_TEMPLATE = [['..O..', 70 | '..O..', 71 | '..O..', 72 | '..O..', 73 | '.....'], 74 | ['.....', 75 | '.....', 76 | 'OOOO.', 77 | '.....', 78 | '.....']] 79 | 80 | O_SHAPE_TEMPLATE = [['.....', 81 | '.....', 82 | '.OO..', 83 | '.OO..', 84 | '.....']] 85 | 86 | J_SHAPE_TEMPLATE = [['.....', 87 | '.O...', 88 | '.OOO.', 89 | '.....', 90 | '.....'], 91 | ['.....', 92 | '..OO.', 93 | '..O..', 94 | '..O..', 95 | '.....'], 96 | ['.....', 97 | '.....', 98 | '.OOO.', 99 | '...O.', 100 | '.....'], 101 | ['.....', 102 | '..O..', 103 | '..O..', 104 | '.OO..', 105 | '.....']] 106 | 107 | L_SHAPE_TEMPLATE = [['.....', 108 | '...O.', 109 | '.OOO.', 110 | '.....', 111 | '.....'], 112 | ['.....', 113 | '..O..', 114 | '..O..', 115 | '..OO.', 116 | '.....'], 117 | ['.....', 118 | '.....', 119 | '.OOO.', 120 | '.O...', 121 | '.....'], 122 | ['.....', 123 | '.OO..', 124 | '..O..', 125 | '..O..', 126 | '.....']] 127 | 128 | T_SHAPE_TEMPLATE = [['.....', 129 | '..O..', 130 | '.OOO.', 131 | '.....', 132 | '.....'], 133 | ['.....', 134 | '..O..', 135 | '..OO.', 136 | '..O..', 137 | '.....'], 138 | ['.....', 139 | '.....', 140 | '.OOO.', 141 | '..O..', 142 | '.....'], 143 | ['.....', 144 | '..O..', 145 | '.OO..', 146 | '..O..', 147 | '.....']] 148 | 149 | PIECES = {'S': S_SHAPE_TEMPLATE, 150 | 'Z': Z_SHAPE_TEMPLATE, 151 | 'J': J_SHAPE_TEMPLATE, 152 | 'L': L_SHAPE_TEMPLATE, 153 | 'I': I_SHAPE_TEMPLATE, 154 | 'O': O_SHAPE_TEMPLATE, 155 | 'T': T_SHAPE_TEMPLATE} 156 | 157 | 158 | def main(): 159 | global FPSCLOCK, DISPLAYSURF, BASICFONT, BIGFONT 160 | pygame.init() 161 | FPSCLOCK = pygame.time.Clock() 162 | DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) 163 | BASICFONT = pygame.font.Font('freesansbold.ttf', 18) 164 | BIGFONT = pygame.font.Font('freesansbold.ttf', 100) 165 | pygame.display.set_caption('Tetromino') 166 | 167 | showTextScreen('Tetromino') 168 | while True: # game loop 169 | if random.randint(0, 1) == 0: 170 | pygame.mixer.music.load('tetrisb.mid') 171 | else: 172 | pygame.mixer.music.load('tetrisc.mid') 173 | pygame.mixer.music.play(-1, 0.0) 174 | runGame() 175 | pygame.mixer.music.stop() 176 | showTextScreen('Game Over') 177 | 178 | 179 | def runGame(): 180 | # setup variables for the start of the game 181 | board = getBlankBoard() 182 | lastMoveDownTime = time.time() 183 | lastMoveSidewaysTime = time.time() 184 | lastFallTime = time.time() 185 | movingDown = False # note: there is no movingUp variable 186 | movingLeft = False 187 | movingRight = False 188 | score = 0 189 | level, fallFreq = calculateLevelAndFallFreq(score) 190 | 191 | fallingPiece = getNewPiece() 192 | nextPiece = getNewPiece() 193 | 194 | while True: # game loop 195 | if fallingPiece == None: 196 | # No falling piece in play, so start a new piece at the top 197 | fallingPiece = nextPiece 198 | nextPiece = getNewPiece() 199 | lastFallTime = time.time() # reset lastFallTime 200 | 201 | if not isValidPosition(board, fallingPiece): 202 | return # can't fit a new piece on the board, so game over 203 | 204 | checkForQuit() 205 | for event in pygame.event.get(): # event handling loop 206 | if event.type == KEYUP: 207 | if (event.key == K_p): 208 | # Pausing the game 209 | DISPLAYSURF.fill(BGCOLOR) 210 | pygame.mixer.music.stop() 211 | showTextScreen('Paused') # pause until a key press 212 | pygame.mixer.music.play(-1, 0.0) 213 | lastFallTime = time.time() 214 | lastMoveDownTime = time.time() 215 | lastMoveSidewaysTime = time.time() 216 | elif (event.key == K_LEFT or event.key == K_a): 217 | movingLeft = False 218 | elif (event.key == K_RIGHT or event.key == K_d): 219 | movingRight = False 220 | elif (event.key == K_DOWN or event.key == K_s): 221 | movingDown = False 222 | 223 | elif event.type == KEYDOWN: 224 | # moving the piece sideways 225 | if (event.key == K_LEFT or event.key == K_a) and isValidPosition(board, fallingPiece, adjX=-1): 226 | fallingPiece['x'] -= 1 227 | movingLeft = True 228 | movingRight = False 229 | lastMoveSidewaysTime = time.time() 230 | 231 | elif (event.key == K_RIGHT or event.key == K_d) and isValidPosition(board, fallingPiece, adjX=1): 232 | fallingPiece['x'] += 1 233 | movingRight = True 234 | movingLeft = False 235 | lastMoveSidewaysTime = time.time() 236 | 237 | # rotating the piece (if there is room to rotate) 238 | elif (event.key == K_UP or event.key == K_w): 239 | fallingPiece['rotation'] = (fallingPiece['rotation'] + 1) % len(PIECES[fallingPiece['shape']]) 240 | if not isValidPosition(board, fallingPiece): 241 | fallingPiece['rotation'] = (fallingPiece['rotation'] - 1) % len(PIECES[fallingPiece['shape']]) 242 | elif (event.key == K_q): # rotate the other direction 243 | fallingPiece['rotation'] = (fallingPiece['rotation'] - 1) % len(PIECES[fallingPiece['shape']]) 244 | if not isValidPosition(board, fallingPiece): 245 | fallingPiece['rotation'] = (fallingPiece['rotation'] + 1) % len(PIECES[fallingPiece['shape']]) 246 | 247 | # making the piece fall faster with the down key 248 | elif (event.key == K_DOWN or event.key == K_s): 249 | movingDown = True 250 | if isValidPosition(board, fallingPiece, adjY=1): 251 | fallingPiece['y'] += 1 252 | lastMoveDownTime = time.time() 253 | 254 | # move the current piece all the way down 255 | elif event.key == K_SPACE: 256 | movingDown = False 257 | movingLeft = False 258 | movingRight = False 259 | for i in range(1, BOARDHEIGHT): 260 | if not isValidPosition(board, fallingPiece, adjY=i): 261 | break 262 | fallingPiece['y'] += i - 1 263 | 264 | # handle moving the piece because of user input 265 | if (movingLeft or movingRight) and time.time() - lastMoveSidewaysTime > MOVESIDEWAYSFREQ: 266 | if movingLeft and isValidPosition(board, fallingPiece, adjX=-1): 267 | fallingPiece['x'] -= 1 268 | elif movingRight and isValidPosition(board, fallingPiece, adjX=1): 269 | fallingPiece['x'] += 1 270 | lastMoveSidewaysTime = time.time() 271 | 272 | if movingDown and time.time() - lastMoveDownTime > MOVEDOWNFREQ and isValidPosition(board, fallingPiece, adjY=1): 273 | fallingPiece['y'] += 1 274 | lastMoveDownTime = time.time() 275 | 276 | # let the piece fall if it is time to fall 277 | if time.time() - lastFallTime > fallFreq: 278 | # see if the piece has landed 279 | if not isValidPosition(board, fallingPiece, adjY=1): 280 | # falling piece has landed, set it on the board 281 | addToBoard(board, fallingPiece) 282 | score += removeCompleteLines(board) 283 | level, fallFreq = calculateLevelAndFallFreq(score) 284 | fallingPiece = None 285 | else: 286 | # piece did not land, just move the piece down 287 | fallingPiece['y'] += 1 288 | lastFallTime = time.time() 289 | 290 | # drawing everything on the screen 291 | DISPLAYSURF.fill(BGCOLOR) 292 | drawBoard(board) 293 | drawStatus(score, level) 294 | drawNextPiece(nextPiece) 295 | if fallingPiece != None: 296 | drawPiece(fallingPiece) 297 | 298 | pygame.display.update() 299 | FPSCLOCK.tick(FPS) 300 | 301 | 302 | def makeTextObjs(text, font, color): 303 | surf = font.render(text, True, color) 304 | return surf, surf.get_rect() 305 | 306 | 307 | def terminate(): 308 | pygame.quit() 309 | sys.exit() 310 | 311 | 312 | def checkForKeyPress(): 313 | # Go through event queue looking for a KEYUP event. 314 | # Grab KEYDOWN events to remove them from the event queue. 315 | checkForQuit() 316 | 317 | for event in pygame.event.get([KEYDOWN, KEYUP]): 318 | if event.type == KEYDOWN: 319 | continue 320 | return event.key 321 | return None 322 | 323 | 324 | def showTextScreen(text): 325 | # This function displays large text in the 326 | # center of the screen until a key is pressed. 327 | # Draw the text drop shadow 328 | titleSurf, titleRect = makeTextObjs(text, BIGFONT, TEXTSHADOWCOLOR) 329 | titleRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2)) 330 | DISPLAYSURF.blit(titleSurf, titleRect) 331 | 332 | # Draw the text 333 | titleSurf, titleRect = makeTextObjs(text, BIGFONT, TEXTCOLOR) 334 | titleRect.center = (int(WINDOWWIDTH / 2) - 3, int(WINDOWHEIGHT / 2) - 3) 335 | DISPLAYSURF.blit(titleSurf, titleRect) 336 | 337 | # Draw the additional "Press a key to play." text. 338 | pressKeySurf, pressKeyRect = makeTextObjs('Press a key to play.', BASICFONT, TEXTCOLOR) 339 | pressKeyRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) + 100) 340 | DISPLAYSURF.blit(pressKeySurf, pressKeyRect) 341 | 342 | while checkForKeyPress() == None: 343 | pygame.display.update() 344 | FPSCLOCK.tick() 345 | 346 | 347 | def checkForQuit(): 348 | for event in pygame.event.get(QUIT): # get all the QUIT events 349 | terminate() # terminate if any QUIT events are present 350 | for event in pygame.event.get(KEYUP): # get all the KEYUP events 351 | if event.key == K_ESCAPE: 352 | terminate() # terminate if the KEYUP event was for the Esc key 353 | pygame.event.post(event) # put the other KEYUP event objects back 354 | 355 | 356 | def calculateLevelAndFallFreq(score): 357 | # Based on the score, return the level the player is on and 358 | # how many seconds pass until a falling piece falls one space. 359 | level = int(score / 10) + 1 360 | fallFreq = 0.27 - (level * 0.02) 361 | return level, fallFreq 362 | 363 | def getNewPiece(): 364 | # return a random new piece in a random rotation and color 365 | shape = random.choice(list(PIECES.keys())) 366 | newPiece = {'shape': shape, 367 | 'rotation': random.randint(0, len(PIECES[shape]) - 1), 368 | 'x': int(BOARDWIDTH / 2) - int(TEMPLATEWIDTH / 2), 369 | 'y': -2, # start it above the board (i.e. less than 0) 370 | 'color': random.randint(0, len(COLORS)-1)} 371 | return newPiece 372 | 373 | 374 | def addToBoard(board, piece): 375 | # fill in the board based on piece's location, shape, and rotation 376 | for x in range(TEMPLATEWIDTH): 377 | for y in range(TEMPLATEHEIGHT): 378 | if PIECES[piece['shape']][piece['rotation']][y][x] != BLANK: 379 | board[x + piece['x']][y + piece['y']] = piece['color'] 380 | 381 | 382 | def getBlankBoard(): 383 | # create and return a new blank board data structure 384 | board = [] 385 | for i in range(BOARDWIDTH): 386 | board.append([BLANK] * BOARDHEIGHT) 387 | return board 388 | 389 | 390 | def isOnBoard(x, y): 391 | return x >= 0 and x < BOARDWIDTH and y < BOARDHEIGHT 392 | 393 | 394 | def isValidPosition(board, piece, adjX=0, adjY=0): 395 | # Return True if the piece is within the board and not colliding 396 | for x in range(TEMPLATEWIDTH): 397 | for y in range(TEMPLATEHEIGHT): 398 | isAboveBoard = y + piece['y'] + adjY < 0 399 | if isAboveBoard or PIECES[piece['shape']][piece['rotation']][y][x] == BLANK: 400 | continue 401 | if not isOnBoard(x + piece['x'] + adjX, y + piece['y'] + adjY): 402 | return False 403 | if board[x + piece['x'] + adjX][y + piece['y'] + adjY] != BLANK: 404 | return False 405 | return True 406 | 407 | def isCompleteLine(board, y): 408 | # Return True if the line filled with boxes with no gaps. 409 | for x in range(BOARDWIDTH): 410 | if board[x][y] == BLANK: 411 | return False 412 | return True 413 | 414 | 415 | def removeCompleteLines(board): 416 | # Remove any completed lines on the board, move everything above them down, and return the number of complete lines. 417 | numLinesRemoved = 0 418 | y = BOARDHEIGHT - 1 # start y at the bottom of the board 419 | while y >= 0: 420 | if isCompleteLine(board, y): 421 | # Remove the line and pull boxes down by one line. 422 | for pullDownY in range(y, 0, -1): 423 | for x in range(BOARDWIDTH): 424 | board[x][pullDownY] = board[x][pullDownY-1] 425 | # Set very top line to blank. 426 | for x in range(BOARDWIDTH): 427 | board[x][0] = BLANK 428 | numLinesRemoved += 1 429 | # Note on the next iteration of the loop, y is the same. 430 | # This is so that if the line that was pulled down is also 431 | # complete, it will be removed. 432 | else: 433 | y -= 1 # move on to check next row up 434 | return numLinesRemoved 435 | 436 | 437 | def convertToPixelCoords(boxx, boxy): 438 | # Convert the given xy coordinates of the board to xy 439 | # coordinates of the location on the screen. 440 | return (XMARGIN + (boxx * BOXSIZE)), (TOPMARGIN + (boxy * BOXSIZE)) 441 | 442 | 443 | def drawBox(boxx, boxy, color, pixelx=None, pixely=None): 444 | # draw a single box (each tetromino piece has four boxes) 445 | # at xy coordinates on the board. Or, if pixelx & pixely 446 | # are specified, draw to the pixel coordinates stored in 447 | # pixelx & pixely (this is used for the "Next" piece). 448 | if color == BLANK: 449 | return 450 | if pixelx == None and pixely == None: 451 | pixelx, pixely = convertToPixelCoords(boxx, boxy) 452 | pygame.draw.rect(DISPLAYSURF, COLORS[color], (pixelx + 1, pixely + 1, BOXSIZE - 1, BOXSIZE - 1)) 453 | pygame.draw.rect(DISPLAYSURF, LIGHTCOLORS[color], (pixelx + 1, pixely + 1, BOXSIZE - 4, BOXSIZE - 4)) 454 | 455 | 456 | def drawBoard(board): 457 | # draw the border around the board 458 | pygame.draw.rect(DISPLAYSURF, BORDERCOLOR, (XMARGIN - 3, TOPMARGIN - 7, (BOARDWIDTH * BOXSIZE) + 8, (BOARDHEIGHT * BOXSIZE) + 8), 5) 459 | 460 | # fill the background of the board 461 | pygame.draw.rect(DISPLAYSURF, BGCOLOR, (XMARGIN, TOPMARGIN, BOXSIZE * BOARDWIDTH, BOXSIZE * BOARDHEIGHT)) 462 | # draw the individual boxes on the board 463 | for x in range(BOARDWIDTH): 464 | for y in range(BOARDHEIGHT): 465 | drawBox(x, y, board[x][y]) 466 | 467 | 468 | def drawStatus(score, level): 469 | # draw the score text 470 | scoreSurf = BASICFONT.render('Score: %s' % score, True, TEXTCOLOR) 471 | scoreRect = scoreSurf.get_rect() 472 | scoreRect.topleft = (WINDOWWIDTH - 150, 20) 473 | DISPLAYSURF.blit(scoreSurf, scoreRect) 474 | 475 | # draw the level text 476 | levelSurf = BASICFONT.render('Level: %s' % level, True, TEXTCOLOR) 477 | levelRect = levelSurf.get_rect() 478 | levelRect.topleft = (WINDOWWIDTH - 150, 50) 479 | DISPLAYSURF.blit(levelSurf, levelRect) 480 | 481 | 482 | def drawPiece(piece, pixelx=None, pixely=None): 483 | shapeToDraw = PIECES[piece['shape']][piece['rotation']] 484 | if pixelx == None and pixely == None: 485 | # if pixelx & pixely hasn't been specified, use the location stored in the piece data structure 486 | pixelx, pixely = convertToPixelCoords(piece['x'], piece['y']) 487 | 488 | # draw each of the boxes that make up the piece 489 | for x in range(TEMPLATEWIDTH): 490 | for y in range(TEMPLATEHEIGHT): 491 | if shapeToDraw[y][x] != BLANK: 492 | drawBox(None, None, piece['color'], pixelx + (x * BOXSIZE), pixely + (y * BOXSIZE)) 493 | 494 | 495 | def drawNextPiece(piece): 496 | # draw the "next" text 497 | nextSurf = BASICFONT.render('Next:', True, TEXTCOLOR) 498 | nextRect = nextSurf.get_rect() 499 | nextRect.topleft = (WINDOWWIDTH - 120, 80) 500 | DISPLAYSURF.blit(nextSurf, nextRect) 501 | # draw the "next" piece 502 | drawPiece(piece, pixelx=WINDOWWIDTH-120, pixely=100) 503 | 504 | 505 | if __name__ == '__main__': 506 | main() -------------------------------------------------------------------------------- /flippy/flippy.py: -------------------------------------------------------------------------------- 1 | # Flippy (an Othello or Reversi clone) 2 | # By Al Sweigart al@inventwithpython.com 3 | # http://inventwithpython.com/pygame 4 | # Released under a "Simplified BSD" license 5 | 6 | # Based on the "reversi.py" code that originally appeared in "Invent 7 | # Your Own Computer Games with Python", chapter 15: 8 | # http://inventwithpython.com/chapter15.html 9 | 10 | import random, sys, pygame, time, copy 11 | from pygame.locals import * 12 | 13 | FPS = 10 # frames per second to update the screen 14 | WINDOWWIDTH = 640 # width of the program's window, in pixels 15 | WINDOWHEIGHT = 480 # height in pixels 16 | SPACESIZE = 50 # width & height of each space on the board, in pixels 17 | BOARDWIDTH = 8 # how many columns of spaces on the game board 18 | BOARDHEIGHT = 8 # how many rows of spaces on the game board 19 | WHITE_TILE = 'WHITE_TILE' # an arbitrary but unique value 20 | BLACK_TILE = 'BLACK_TILE' # an arbitrary but unique value 21 | EMPTY_SPACE = 'EMPTY_SPACE' # an arbitrary but unique value 22 | HINT_TILE = 'HINT_TILE' # an arbitrary but unique value 23 | ANIMATIONSPEED = 25 # integer from 1 to 100, higher is faster animation 24 | 25 | # Amount of space on the left & right side (XMARGIN) or above and below 26 | # (YMARGIN) the game board, in pixels. 27 | XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * SPACESIZE)) / 2) 28 | YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * SPACESIZE)) / 2) 29 | 30 | # R G B 31 | WHITE = (255, 255, 255) 32 | BLACK = ( 0, 0, 0) 33 | GREEN = ( 0, 155, 0) 34 | BRIGHTBLUE = ( 0, 50, 255) 35 | BROWN = (174, 94, 0) 36 | 37 | TEXTBGCOLOR1 = BRIGHTBLUE 38 | TEXTBGCOLOR2 = GREEN 39 | GRIDLINECOLOR = BLACK 40 | TEXTCOLOR = WHITE 41 | HINTCOLOR = BROWN 42 | 43 | 44 | def main(): 45 | global MAINCLOCK, DISPLAYSURF, FONT, BIGFONT, BGIMAGE 46 | 47 | pygame.init() 48 | MAINCLOCK = pygame.time.Clock() 49 | DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) 50 | pygame.display.set_caption('Flippy') 51 | FONT = pygame.font.Font('freesansbold.ttf', 16) 52 | BIGFONT = pygame.font.Font('freesansbold.ttf', 32) 53 | 54 | # Set up the background image. 55 | boardImage = pygame.image.load('flippyboard.png') 56 | # Use smoothscale() to stretch the board image to fit the entire board: 57 | boardImage = pygame.transform.smoothscale(boardImage, (BOARDWIDTH * SPACESIZE, BOARDHEIGHT * SPACESIZE)) 58 | boardImageRect = boardImage.get_rect() 59 | boardImageRect.topleft = (XMARGIN, YMARGIN) 60 | BGIMAGE = pygame.image.load('flippybackground.png') 61 | # Use smoothscale() to stretch the background image to fit the entire window: 62 | BGIMAGE = pygame.transform.smoothscale(BGIMAGE, (WINDOWWIDTH, WINDOWHEIGHT)) 63 | BGIMAGE.blit(boardImage, boardImageRect) 64 | 65 | # Run the main game. 66 | while True: 67 | if runGame() == False: 68 | break 69 | 70 | 71 | def runGame(): 72 | # Plays a single game of reversi each time this function is called. 73 | 74 | # Reset the board and game. 75 | mainBoard = getNewBoard() 76 | resetBoard(mainBoard) 77 | showHints = False 78 | turn = random.choice(['computer', 'player']) 79 | 80 | # Draw the starting board and ask the player what color they want. 81 | drawBoard(mainBoard) 82 | playerTile, computerTile = enterPlayerTile() 83 | 84 | # Make the Surface and Rect objects for the "New Game" and "Hints" buttons 85 | newGameSurf = FONT.render('New Game', True, TEXTCOLOR, TEXTBGCOLOR2) 86 | newGameRect = newGameSurf.get_rect() 87 | newGameRect.topright = (WINDOWWIDTH - 8, 10) 88 | hintsSurf = FONT.render('Hints', True, TEXTCOLOR, TEXTBGCOLOR2) 89 | hintsRect = hintsSurf.get_rect() 90 | hintsRect.topright = (WINDOWWIDTH - 8, 40) 91 | 92 | while True: # main game loop 93 | # Keep looping for player and computer's turns. 94 | if turn == 'player': 95 | # Player's turn: 96 | if getValidMoves(mainBoard, playerTile) == []: 97 | # If it's the player's turn but they 98 | # can't move, then end the game. 99 | break 100 | movexy = None 101 | while movexy == None: 102 | # Keep looping until the player clicks on a valid space. 103 | 104 | # Determine which board data structure to use for display. 105 | if showHints: 106 | boardToDraw = getBoardWithValidMoves(mainBoard, playerTile) 107 | else: 108 | boardToDraw = mainBoard 109 | 110 | checkForQuit() 111 | for event in pygame.event.get(): # event handling loop 112 | if event.type == MOUSEBUTTONUP: 113 | # Handle mouse click events 114 | mousex, mousey = event.pos 115 | if newGameRect.collidepoint( (mousex, mousey) ): 116 | # Start a new game 117 | return True 118 | elif hintsRect.collidepoint( (mousex, mousey) ): 119 | # Toggle hints mode 120 | showHints = not showHints 121 | # movexy is set to a two-item tuple XY coordinate, or None value 122 | movexy = getSpaceClicked(mousex, mousey) 123 | if movexy != None and not isValidMove(mainBoard, playerTile, movexy[0], movexy[1]): 124 | movexy = None 125 | 126 | # Draw the game board. 127 | drawBoard(boardToDraw) 128 | drawInfo(boardToDraw, playerTile, computerTile, turn) 129 | 130 | # Draw the "New Game" and "Hints" buttons. 131 | DISPLAYSURF.blit(newGameSurf, newGameRect) 132 | DISPLAYSURF.blit(hintsSurf, hintsRect) 133 | 134 | MAINCLOCK.tick(FPS) 135 | pygame.display.update() 136 | 137 | # Make the move and end the turn. 138 | makeMove(mainBoard, playerTile, movexy[0], movexy[1], True) 139 | if getValidMoves(mainBoard, computerTile) != []: 140 | # Only set for the computer's turn if it can make a move. 141 | turn = 'computer' 142 | 143 | else: 144 | # Computer's turn: 145 | if getValidMoves(mainBoard, computerTile) == []: 146 | # If it was set to be the computer's turn but 147 | # they can't move, then end the game. 148 | break 149 | 150 | # Draw the board. 151 | drawBoard(mainBoard) 152 | drawInfo(mainBoard, playerTile, computerTile, turn) 153 | 154 | # Draw the "New Game" and "Hints" buttons. 155 | DISPLAYSURF.blit(newGameSurf, newGameRect) 156 | DISPLAYSURF.blit(hintsSurf, hintsRect) 157 | 158 | # Make it look like the computer is thinking by pausing a bit. 159 | pauseUntil = time.time() + random.randint(5, 15) * 0.1 160 | while time.time() < pauseUntil: 161 | pygame.display.update() 162 | 163 | # Make the move and end the turn. 164 | x, y = getComputerMove(mainBoard, computerTile) 165 | makeMove(mainBoard, computerTile, x, y, True) 166 | if getValidMoves(mainBoard, playerTile) != []: 167 | # Only set for the player's turn if they can make a move. 168 | turn = 'player' 169 | 170 | # Display the final score. 171 | drawBoard(mainBoard) 172 | scores = getScoreOfBoard(mainBoard) 173 | 174 | # Determine the text of the message to display. 175 | if scores[playerTile] > scores[computerTile]: 176 | text = 'You beat the computer by %s points! Congratulations!' % \ 177 | (scores[playerTile] - scores[computerTile]) 178 | elif scores[playerTile] < scores[computerTile]: 179 | text = 'You lost. The computer beat you by %s points.' % \ 180 | (scores[computerTile] - scores[playerTile]) 181 | else: 182 | text = 'The game was a tie!' 183 | 184 | textSurf = FONT.render(text, True, TEXTCOLOR, TEXTBGCOLOR1) 185 | textRect = textSurf.get_rect() 186 | textRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2)) 187 | DISPLAYSURF.blit(textSurf, textRect) 188 | 189 | # Display the "Play again?" text with Yes and No buttons. 190 | text2Surf = BIGFONT.render('Play again?', True, TEXTCOLOR, TEXTBGCOLOR1) 191 | text2Rect = text2Surf.get_rect() 192 | text2Rect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) + 50) 193 | 194 | # Make "Yes" button. 195 | yesSurf = BIGFONT.render('Yes', True, TEXTCOLOR, TEXTBGCOLOR1) 196 | yesRect = yesSurf.get_rect() 197 | yesRect.center = (int(WINDOWWIDTH / 2) - 60, int(WINDOWHEIGHT / 2) + 90) 198 | 199 | # Make "No" button. 200 | noSurf = BIGFONT.render('No', True, TEXTCOLOR, TEXTBGCOLOR1) 201 | noRect = noSurf.get_rect() 202 | noRect.center = (int(WINDOWWIDTH / 2) + 60, int(WINDOWHEIGHT / 2) + 90) 203 | 204 | while True: 205 | # Process events until the user clicks on Yes or No. 206 | checkForQuit() 207 | for event in pygame.event.get(): # event handling loop 208 | if event.type == MOUSEBUTTONUP: 209 | mousex, mousey = event.pos 210 | if yesRect.collidepoint( (mousex, mousey) ): 211 | return True 212 | elif noRect.collidepoint( (mousex, mousey) ): 213 | return False 214 | DISPLAYSURF.blit(textSurf, textRect) 215 | DISPLAYSURF.blit(text2Surf, text2Rect) 216 | DISPLAYSURF.blit(yesSurf, yesRect) 217 | DISPLAYSURF.blit(noSurf, noRect) 218 | pygame.display.update() 219 | MAINCLOCK.tick(FPS) 220 | 221 | 222 | def translateBoardToPixelCoord(x, y): 223 | return XMARGIN + x * SPACESIZE + int(SPACESIZE / 2), YMARGIN + y * SPACESIZE + int(SPACESIZE / 2) 224 | 225 | 226 | def animateTileChange(tilesToFlip, tileColor, additionalTile): 227 | # Draw the additional tile that was just laid down. (Otherwise we'd 228 | # have to completely redraw the board & the board info.) 229 | if tileColor == WHITE_TILE: 230 | additionalTileColor = WHITE 231 | else: 232 | additionalTileColor = BLACK 233 | additionalTileX, additionalTileY = translateBoardToPixelCoord(additionalTile[0], additionalTile[1]) 234 | pygame.draw.circle(DISPLAYSURF, additionalTileColor, (additionalTileX, additionalTileY), int(SPACESIZE / 2) - 4) 235 | pygame.display.update() 236 | 237 | for rgbValues in range(0, 255, int(ANIMATIONSPEED * 2.55)): 238 | if rgbValues > 255: 239 | rgbValues = 255 240 | elif rgbValues < 0: 241 | rgbValues = 0 242 | 243 | if tileColor == WHITE_TILE: 244 | color = tuple([rgbValues] * 3) # rgbValues goes from 0 to 255 245 | elif tileColor == BLACK_TILE: 246 | color = tuple([255 - rgbValues] * 3) # rgbValues goes from 255 to 0 247 | 248 | for x, y in tilesToFlip: 249 | centerx, centery = translateBoardToPixelCoord(x, y) 250 | pygame.draw.circle(DISPLAYSURF, color, (centerx, centery), int(SPACESIZE / 2) - 4) 251 | pygame.display.update() 252 | MAINCLOCK.tick(FPS) 253 | checkForQuit() 254 | 255 | 256 | def drawBoard(board): 257 | # Draw background of board. 258 | DISPLAYSURF.blit(BGIMAGE, BGIMAGE.get_rect()) 259 | 260 | # Draw grid lines of the board. 261 | for x in range(BOARDWIDTH + 1): 262 | # Draw the horizontal lines. 263 | startx = (x * SPACESIZE) + XMARGIN 264 | starty = YMARGIN 265 | endx = (x * SPACESIZE) + XMARGIN 266 | endy = YMARGIN + (BOARDHEIGHT * SPACESIZE) 267 | pygame.draw.line(DISPLAYSURF, GRIDLINECOLOR, (startx, starty), (endx, endy)) 268 | for y in range(BOARDHEIGHT + 1): 269 | # Draw the vertical lines. 270 | startx = XMARGIN 271 | starty = (y * SPACESIZE) + YMARGIN 272 | endx = XMARGIN + (BOARDWIDTH * SPACESIZE) 273 | endy = (y * SPACESIZE) + YMARGIN 274 | pygame.draw.line(DISPLAYSURF, GRIDLINECOLOR, (startx, starty), (endx, endy)) 275 | 276 | # Draw the black & white tiles or hint spots. 277 | for x in range(BOARDWIDTH): 278 | for y in range(BOARDHEIGHT): 279 | centerx, centery = translateBoardToPixelCoord(x, y) 280 | if board[x][y] == WHITE_TILE or board[x][y] == BLACK_TILE: 281 | if board[x][y] == WHITE_TILE: 282 | tileColor = WHITE 283 | else: 284 | tileColor = BLACK 285 | pygame.draw.circle(DISPLAYSURF, tileColor, (centerx, centery), int(SPACESIZE / 2) - 4) 286 | if board[x][y] == HINT_TILE: 287 | pygame.draw.rect(DISPLAYSURF, HINTCOLOR, (centerx - 4, centery - 4, 8, 8)) 288 | 289 | 290 | def getSpaceClicked(mousex, mousey): 291 | # Return a tuple of two integers of the board space coordinates where 292 | # the mouse was clicked. (Or returns None not in any space.) 293 | for x in range(BOARDWIDTH): 294 | for y in range(BOARDHEIGHT): 295 | if mousex > x * SPACESIZE + XMARGIN and \ 296 | mousex < (x + 1) * SPACESIZE + XMARGIN and \ 297 | mousey > y * SPACESIZE + YMARGIN and \ 298 | mousey < (y + 1) * SPACESIZE + YMARGIN: 299 | return (x, y) 300 | return None 301 | 302 | 303 | def drawInfo(board, playerTile, computerTile, turn): 304 | # Draws scores and whose turn it is at the bottom of the screen. 305 | scores = getScoreOfBoard(board) 306 | scoreSurf = FONT.render("Player Score: %s Computer Score: %s %s's Turn" % (str(scores[playerTile]), str(scores[computerTile]), turn.title()), True, TEXTCOLOR) 307 | scoreRect = scoreSurf.get_rect() 308 | scoreRect.bottomleft = (10, WINDOWHEIGHT - 5) 309 | DISPLAYSURF.blit(scoreSurf, scoreRect) 310 | 311 | 312 | def resetBoard(board): 313 | # Blanks out the board it is passed, and sets up starting tiles. 314 | for x in range(BOARDWIDTH): 315 | for y in range(BOARDHEIGHT): 316 | board[x][y] = EMPTY_SPACE 317 | 318 | # Add starting pieces to the center 319 | board[3][3] = WHITE_TILE 320 | board[3][4] = BLACK_TILE 321 | board[4][3] = BLACK_TILE 322 | board[4][4] = WHITE_TILE 323 | 324 | 325 | def getNewBoard(): 326 | # Creates a brand new, empty board data structure. 327 | board = [] 328 | for i in range(BOARDWIDTH): 329 | board.append([EMPTY_SPACE] * BOARDHEIGHT) 330 | 331 | return board 332 | 333 | 334 | def isValidMove(board, tile, xstart, ystart): 335 | # Returns False if the player's move is invalid. If it is a valid 336 | # move, returns a list of spaces of the captured pieces. 337 | if board[xstart][ystart] != EMPTY_SPACE or not isOnBoard(xstart, ystart): 338 | return False 339 | 340 | board[xstart][ystart] = tile # temporarily set the tile on the board. 341 | 342 | if tile == WHITE_TILE: 343 | otherTile = BLACK_TILE 344 | else: 345 | otherTile = WHITE_TILE 346 | 347 | tilesToFlip = [] 348 | # check each of the eight directions: 349 | for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]: 350 | x, y = xstart, ystart 351 | x += xdirection 352 | y += ydirection 353 | if isOnBoard(x, y) and board[x][y] == otherTile: 354 | # The piece belongs to the other player next to our piece. 355 | x += xdirection 356 | y += ydirection 357 | if not isOnBoard(x, y): 358 | continue 359 | while board[x][y] == otherTile: 360 | x += xdirection 361 | y += ydirection 362 | if not isOnBoard(x, y): 363 | break # break out of while loop, continue in for loop 364 | if not isOnBoard(x, y): 365 | continue 366 | if board[x][y] == tile: 367 | # There are pieces to flip over. Go in the reverse 368 | # direction until we reach the original space, noting all 369 | # the tiles along the way. 370 | while True: 371 | x -= xdirection 372 | y -= ydirection 373 | if x == xstart and y == ystart: 374 | break 375 | tilesToFlip.append([x, y]) 376 | 377 | board[xstart][ystart] = EMPTY_SPACE # make space empty 378 | if len(tilesToFlip) == 0: # If no tiles flipped, this move is invalid 379 | return False 380 | return tilesToFlip 381 | 382 | 383 | def isOnBoard(x, y): 384 | # Returns True if the coordinates are located on the board. 385 | return x >= 0 and x < BOARDWIDTH and y >= 0 and y < BOARDHEIGHT 386 | 387 | 388 | def getBoardWithValidMoves(board, tile): 389 | # Returns a new board with hint markings. 390 | dupeBoard = copy.deepcopy(board) 391 | 392 | for x, y in getValidMoves(dupeBoard, tile): 393 | dupeBoard[x][y] = HINT_TILE 394 | return dupeBoard 395 | 396 | 397 | def getValidMoves(board, tile): 398 | # Returns a list of (x,y) tuples of all valid moves. 399 | validMoves = [] 400 | 401 | for x in range(BOARDWIDTH): 402 | for y in range(BOARDHEIGHT): 403 | if isValidMove(board, tile, x, y) != False: 404 | validMoves.append((x, y)) 405 | return validMoves 406 | 407 | 408 | def getScoreOfBoard(board): 409 | # Determine the score by counting the tiles. 410 | xscore = 0 411 | oscore = 0 412 | for x in range(BOARDWIDTH): 413 | for y in range(BOARDHEIGHT): 414 | if board[x][y] == WHITE_TILE: 415 | xscore += 1 416 | if board[x][y] == BLACK_TILE: 417 | oscore += 1 418 | return {WHITE_TILE:xscore, BLACK_TILE:oscore} 419 | 420 | 421 | def enterPlayerTile(): 422 | # Draws the text and handles the mouse click events for letting 423 | # the player choose which color they want to be. Returns 424 | # [WHITE_TILE, BLACK_TILE] if the player chooses to be White, 425 | # [BLACK_TILE, WHITE_TILE] if Black. 426 | 427 | # Create the text. 428 | textSurf = FONT.render('Do you want to be white or black?', True, TEXTCOLOR, TEXTBGCOLOR1) 429 | textRect = textSurf.get_rect() 430 | textRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2)) 431 | 432 | xSurf = BIGFONT.render('White', True, TEXTCOLOR, TEXTBGCOLOR1) 433 | xRect = xSurf.get_rect() 434 | xRect.center = (int(WINDOWWIDTH / 2) - 60, int(WINDOWHEIGHT / 2) + 40) 435 | 436 | oSurf = BIGFONT.render('Black', True, TEXTCOLOR, TEXTBGCOLOR1) 437 | oRect = oSurf.get_rect() 438 | oRect.center = (int(WINDOWWIDTH / 2) + 60, int(WINDOWHEIGHT / 2) + 40) 439 | 440 | while True: 441 | # Keep looping until the player has clicked on a color. 442 | checkForQuit() 443 | for event in pygame.event.get(): # event handling loop 444 | if event.type == MOUSEBUTTONUP: 445 | mousex, mousey = event.pos 446 | if xRect.collidepoint( (mousex, mousey) ): 447 | return [WHITE_TILE, BLACK_TILE] 448 | elif oRect.collidepoint( (mousex, mousey) ): 449 | return [BLACK_TILE, WHITE_TILE] 450 | 451 | # Draw the screen. 452 | DISPLAYSURF.blit(textSurf, textRect) 453 | DISPLAYSURF.blit(xSurf, xRect) 454 | DISPLAYSURF.blit(oSurf, oRect) 455 | pygame.display.update() 456 | MAINCLOCK.tick(FPS) 457 | 458 | 459 | def makeMove(board, tile, xstart, ystart, realMove=False): 460 | # Place the tile on the board at xstart, ystart, and flip tiles 461 | # Returns False if this is an invalid move, True if it is valid. 462 | tilesToFlip = isValidMove(board, tile, xstart, ystart) 463 | 464 | if tilesToFlip == False: 465 | return False 466 | 467 | board[xstart][ystart] = tile 468 | 469 | if realMove: 470 | animateTileChange(tilesToFlip, tile, (xstart, ystart)) 471 | 472 | for x, y in tilesToFlip: 473 | board[x][y] = tile 474 | return True 475 | 476 | 477 | def isOnCorner(x, y): 478 | # Returns True if the position is in one of the four corners. 479 | return (x == 0 and y == 0) or \ 480 | (x == BOARDWIDTH and y == 0) or \ 481 | (x == 0 and y == BOARDHEIGHT) or \ 482 | (x == BOARDWIDTH and y == BOARDHEIGHT) 483 | 484 | 485 | def getComputerMove(board, computerTile): 486 | # Given a board and the computer's tile, determine where to 487 | # move and return that move as a [x, y] list. 488 | possibleMoves = getValidMoves(board, computerTile) 489 | 490 | # randomize the order of the possible moves 491 | random.shuffle(possibleMoves) 492 | 493 | # always go for a corner if available. 494 | for x, y in possibleMoves: 495 | if isOnCorner(x, y): 496 | return [x, y] 497 | 498 | # Go through all possible moves and remember the best scoring move 499 | bestScore = -1 500 | for x, y in possibleMoves: 501 | dupeBoard = copy.deepcopy(board) 502 | makeMove(dupeBoard, computerTile, x, y) 503 | score = getScoreOfBoard(dupeBoard)[computerTile] 504 | if score > bestScore: 505 | bestMove = [x, y] 506 | bestScore = score 507 | return bestMove 508 | 509 | 510 | def checkForQuit(): 511 | for event in pygame.event.get((QUIT, KEYUP)): # event handling loop 512 | if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE): 513 | pygame.quit() 514 | sys.exit() 515 | 516 | 517 | if __name__ == '__main__': 518 | main() 519 | -------------------------------------------------------------------------------- /gemgem/gemgem.py: -------------------------------------------------------------------------------- 1 | # Gemgem (a Bejeweled clone) 2 | # By Al Sweigart al@inventwithpython.com 3 | # http://inventwithpython.com/pygame 4 | # Released under a "Simplified BSD" license 5 | 6 | """ 7 | This program has "gem data structures", which are basically dictionaries 8 | with the following keys: 9 | 'x' and 'y' - The location of the gem on the board. 0,0 is the top left. 10 | There is also a ROWABOVEBOARD row that 'y' can be set to, 11 | to indicate that it is above the board. 12 | 'direction' - one of the four constant variables UP, DOWN, LEFT, RIGHT. 13 | This is the direction the gem is moving. 14 | 'imageNum' - The integer index into GEMIMAGES to denote which image 15 | this gem uses. 16 | """ 17 | 18 | import random, time, pygame, sys, copy 19 | from pygame.locals import * 20 | 21 | FPS = 30 # frames per second to update the screen 22 | WINDOWWIDTH = 600 # width of the program's window, in pixels 23 | WINDOWHEIGHT = 600 # height in pixels 24 | 25 | BOARDWIDTH = 8 # how many columns in the board 26 | BOARDHEIGHT = 8 # how many rows in the board 27 | GEMIMAGESIZE = 64 # width & height of each space in pixels 28 | 29 | # NUMGEMIMAGES is the number of gem types. You will need .png image 30 | # files named gem0.png, gem1.png, etc. up to gem(N-1).png. 31 | NUMGEMIMAGES = 7 32 | assert NUMGEMIMAGES >= 5 # game needs at least 5 types of gems to work 33 | 34 | # NUMMATCHSOUNDS is the number of different sounds to choose from when 35 | # a match is made. The .wav files are named match0.wav, match1.wav, etc. 36 | NUMMATCHSOUNDS = 6 37 | 38 | MOVERATE = 25 # 1 to 100, larger num means faster animations 39 | DEDUCTSPEED = 0.8 # reduces score by 1 point every DEDUCTSPEED seconds. 40 | 41 | # R G B 42 | PURPLE = (255, 0, 255) 43 | LIGHTBLUE = (170, 190, 255) 44 | BLUE = ( 0, 0, 255) 45 | RED = (255, 100, 100) 46 | BLACK = ( 0, 0, 0) 47 | BROWN = ( 85, 65, 0) 48 | HIGHLIGHTCOLOR = PURPLE # color of the selected gem's border 49 | BGCOLOR = LIGHTBLUE # background color on the screen 50 | GRIDCOLOR = BLUE # color of the game board 51 | GAMEOVERCOLOR = RED # color of the "Game over" text. 52 | GAMEOVERBGCOLOR = BLACK # background color of the "Game over" text. 53 | SCORECOLOR = BROWN # color of the text for the player's score 54 | 55 | # The amount of space to the sides of the board to the edge of the window 56 | # is used several times, so calculate it once here and store in variables. 57 | XMARGIN = int((WINDOWWIDTH - GEMIMAGESIZE * BOARDWIDTH) / 2) 58 | YMARGIN = int((WINDOWHEIGHT - GEMIMAGESIZE * BOARDHEIGHT) / 2) 59 | 60 | # constants for direction values 61 | UP = 'up' 62 | DOWN = 'down' 63 | LEFT = 'left' 64 | RIGHT = 'right' 65 | 66 | EMPTY_SPACE = -1 # an arbitrary, nonpositive value 67 | ROWABOVEBOARD = 'row above board' # an arbitrary, noninteger value 68 | 69 | def main(): 70 | global FPSCLOCK, DISPLAYSURF, GEMIMAGES, GAMESOUNDS, BASICFONT, BOARDRECTS 71 | 72 | # Initial set up. 73 | pygame.init() 74 | FPSCLOCK = pygame.time.Clock() 75 | DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) 76 | pygame.display.set_caption('Gemgem') 77 | BASICFONT = pygame.font.Font('freesansbold.ttf', 36) 78 | 79 | # Load the images 80 | GEMIMAGES = [] 81 | for i in range(1, NUMGEMIMAGES+1): 82 | gemImage = pygame.image.load('gem%s.png' % i) 83 | if gemImage.get_size() != (GEMIMAGESIZE, GEMIMAGESIZE): 84 | gemImage = pygame.transform.smoothscale(gemImage, (GEMIMAGESIZE, GEMIMAGESIZE)) 85 | GEMIMAGES.append(gemImage) 86 | 87 | # Load the sounds. 88 | GAMESOUNDS = {} 89 | GAMESOUNDS['bad swap'] = pygame.mixer.Sound('badswap.wav') 90 | GAMESOUNDS['match'] = [] 91 | for i in range(NUMMATCHSOUNDS): 92 | GAMESOUNDS['match'].append(pygame.mixer.Sound('match%s.wav' % i)) 93 | 94 | # Create pygame.Rect objects for each board space to 95 | # do board-coordinate-to-pixel-coordinate conversions. 96 | BOARDRECTS = [] 97 | for x in range(BOARDWIDTH): 98 | BOARDRECTS.append([]) 99 | for y in range(BOARDHEIGHT): 100 | r = pygame.Rect((XMARGIN + (x * GEMIMAGESIZE), 101 | YMARGIN + (y * GEMIMAGESIZE), 102 | GEMIMAGESIZE, 103 | GEMIMAGESIZE)) 104 | BOARDRECTS[x].append(r) 105 | 106 | while True: 107 | runGame() 108 | 109 | 110 | def runGame(): 111 | # Plays through a single game. When the game is over, this function returns. 112 | 113 | # initalize the board 114 | gameBoard = getBlankBoard() 115 | score = 0 116 | fillBoardAndAnimate(gameBoard, [], score) # Drop the initial gems. 117 | 118 | # initialize variables for the start of a new game 119 | firstSelectedGem = None 120 | lastMouseDownX = None 121 | lastMouseDownY = None 122 | gameIsOver = False 123 | lastScoreDeduction = time.time() 124 | clickContinueTextSurf = None 125 | 126 | while True: # main game loop 127 | clickedSpace = None 128 | for event in pygame.event.get(): # event handling loop 129 | if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE): 130 | pygame.quit() 131 | sys.exit() 132 | elif event.type == KEYUP and event.key == K_BACKSPACE: 133 | return # start a new game 134 | 135 | elif event.type == MOUSEBUTTONUP: 136 | if gameIsOver: 137 | return # after games ends, click to start a new game 138 | 139 | if event.pos == (lastMouseDownX, lastMouseDownY): 140 | # This event is a mouse click, not the end of a mouse drag. 141 | clickedSpace = checkForGemClick(event.pos) 142 | else: 143 | # this is the end of a mouse drag 144 | firstSelectedGem = checkForGemClick((lastMouseDownX, lastMouseDownY)) 145 | clickedSpace = checkForGemClick(event.pos) 146 | if not firstSelectedGem or not clickedSpace: 147 | # if not part of a valid drag, deselect both 148 | firstSelectedGem = None 149 | clickedSpace = None 150 | elif event.type == MOUSEBUTTONDOWN: 151 | # this is the start of a mouse click or mouse drag 152 | lastMouseDownX, lastMouseDownY = event.pos 153 | 154 | if clickedSpace and not firstSelectedGem: 155 | # This was the first gem clicked on. 156 | firstSelectedGem = clickedSpace 157 | elif clickedSpace and firstSelectedGem: 158 | # Two gems have been clicked on and selected. Swap the gems. 159 | firstSwappingGem, secondSwappingGem = getSwappingGems(gameBoard, firstSelectedGem, clickedSpace) 160 | if firstSwappingGem == None and secondSwappingGem == None: 161 | # If both are None, then the gems were not adjacent 162 | firstSelectedGem = None # deselect the first gem 163 | continue 164 | 165 | # Show the swap animation on the screen. 166 | boardCopy = getBoardCopyMinusGems(gameBoard, (firstSwappingGem, secondSwappingGem)) 167 | animateMovingGems(boardCopy, [firstSwappingGem, secondSwappingGem], [], score) 168 | 169 | # Swap the gems in the board data structure. 170 | gameBoard[firstSwappingGem['x']][firstSwappingGem['y']] = secondSwappingGem['imageNum'] 171 | gameBoard[secondSwappingGem['x']][secondSwappingGem['y']] = firstSwappingGem['imageNum'] 172 | 173 | # See if this is a matching move. 174 | matchedGems = findMatchingGems(gameBoard) 175 | if matchedGems == []: 176 | # Was not a matching move; swap the gems back 177 | GAMESOUNDS['bad swap'].play() 178 | animateMovingGems(boardCopy, [firstSwappingGem, secondSwappingGem], [], score) 179 | gameBoard[firstSwappingGem['x']][firstSwappingGem['y']] = firstSwappingGem['imageNum'] 180 | gameBoard[secondSwappingGem['x']][secondSwappingGem['y']] = secondSwappingGem['imageNum'] 181 | else: 182 | # This was a matching move. 183 | scoreAdd = 0 184 | while matchedGems != []: 185 | # Remove matched gems, then pull down the board. 186 | 187 | # points is a list of dicts that tells fillBoardAndAnimate() 188 | # where on the screen to display text to show how many 189 | # points the player got. points is a list because if 190 | # the playergets multiple matches, then multiple points text should appear. 191 | points = [] 192 | for gemSet in matchedGems: 193 | scoreAdd += (10 + (len(gemSet) - 3) * 10) 194 | for gem in gemSet: 195 | gameBoard[gem[0]][gem[1]] = EMPTY_SPACE 196 | points.append({'points': scoreAdd, 197 | 'x': gem[0] * GEMIMAGESIZE + XMARGIN, 198 | 'y': gem[1] * GEMIMAGESIZE + YMARGIN}) 199 | random.choice(GAMESOUNDS['match']).play() 200 | score += scoreAdd 201 | 202 | # Drop the new gems. 203 | fillBoardAndAnimate(gameBoard, points, score) 204 | 205 | # Check if there are any new matches. 206 | matchedGems = findMatchingGems(gameBoard) 207 | firstSelectedGem = None 208 | 209 | if not canMakeMove(gameBoard): 210 | gameIsOver = True 211 | 212 | # Draw the board. 213 | DISPLAYSURF.fill(BGCOLOR) 214 | drawBoard(gameBoard) 215 | if firstSelectedGem != None: 216 | highlightSpace(firstSelectedGem['x'], firstSelectedGem['y']) 217 | if gameIsOver: 218 | if clickContinueTextSurf == None: 219 | # Only render the text once. In future iterations, just 220 | # use the Surface object already in clickContinueTextSurf 221 | clickContinueTextSurf = BASICFONT.render('Final Score: %s (Click to continue)' % (score), 1, GAMEOVERCOLOR, GAMEOVERBGCOLOR) 222 | clickContinueTextRect = clickContinueTextSurf.get_rect() 223 | clickContinueTextRect.center = int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) 224 | DISPLAYSURF.blit(clickContinueTextSurf, clickContinueTextRect) 225 | elif score > 0 and time.time() - lastScoreDeduction > DEDUCTSPEED: 226 | # score drops over time 227 | score -= 1 228 | lastScoreDeduction = time.time() 229 | drawScore(score) 230 | pygame.display.update() 231 | FPSCLOCK.tick(FPS) 232 | 233 | 234 | def getSwappingGems(board, firstXY, secondXY): 235 | # If the gems at the (X, Y) coordinates of the two gems are adjacent, 236 | # then their 'direction' keys are set to the appropriate direction 237 | # value to be swapped with each other. 238 | # Otherwise, (None, None) is returned. 239 | firstGem = {'imageNum': board[firstXY['x']][firstXY['y']], 240 | 'x': firstXY['x'], 241 | 'y': firstXY['y']} 242 | secondGem = {'imageNum': board[secondXY['x']][secondXY['y']], 243 | 'x': secondXY['x'], 244 | 'y': secondXY['y']} 245 | highlightedGem = None 246 | if firstGem['x'] == secondGem['x'] + 1 and firstGem['y'] == secondGem['y']: 247 | firstGem['direction'] = LEFT 248 | secondGem['direction'] = RIGHT 249 | elif firstGem['x'] == secondGem['x'] - 1 and firstGem['y'] == secondGem['y']: 250 | firstGem['direction'] = RIGHT 251 | secondGem['direction'] = LEFT 252 | elif firstGem['y'] == secondGem['y'] + 1 and firstGem['x'] == secondGem['x']: 253 | firstGem['direction'] = UP 254 | secondGem['direction'] = DOWN 255 | elif firstGem['y'] == secondGem['y'] - 1 and firstGem['x'] == secondGem['x']: 256 | firstGem['direction'] = DOWN 257 | secondGem['direction'] = UP 258 | else: 259 | # These gems are not adjacent and can't be swapped. 260 | return None, None 261 | return firstGem, secondGem 262 | 263 | 264 | def getBlankBoard(): 265 | # Create and return a blank board data structure. 266 | board = [] 267 | for x in range(BOARDWIDTH): 268 | board.append([EMPTY_SPACE] * BOARDHEIGHT) 269 | return board 270 | 271 | 272 | def canMakeMove(board): 273 | # Return True if the board is in a state where a matching 274 | # move can be made on it. Otherwise return False. 275 | 276 | # The patterns in oneOffPatterns represent gems that are configured 277 | # in a way where it only takes one move to make a triplet. 278 | oneOffPatterns = (((0,1), (1,0), (2,0)), 279 | ((0,1), (1,1), (2,0)), 280 | ((0,0), (1,1), (2,0)), 281 | ((0,1), (1,0), (2,1)), 282 | ((0,0), (1,0), (2,1)), 283 | ((0,0), (1,1), (2,1)), 284 | ((0,0), (0,2), (0,3)), 285 | ((0,0), (0,1), (0,3))) 286 | 287 | # The x and y variables iterate over each space on the board. 288 | # If we use + to represent the currently iterated space on the 289 | # board, then this pattern: ((0,1), (1,0), (2,0))refers to identical 290 | # gems being set up like this: 291 | # 292 | # +A 293 | # B 294 | # C 295 | # 296 | # That is, gem A is offset from the + by (0,1), gem B is offset 297 | # by (1,0), and gem C is offset by (2,0). In this case, gem A can 298 | # be swapped to the left to form a vertical three-in-a-row triplet. 299 | # 300 | # There are eight possible ways for the gems to be one move 301 | # away from forming a triple, hence oneOffPattern has 8 patterns. 302 | 303 | for x in range(BOARDWIDTH): 304 | for y in range(BOARDHEIGHT): 305 | for pat in oneOffPatterns: 306 | # check each possible pattern of "match in next move" to 307 | # see if a possible move can be made. 308 | if (getGemAt(board, x+pat[0][0], y+pat[0][1]) == \ 309 | getGemAt(board, x+pat[1][0], y+pat[1][1]) == \ 310 | getGemAt(board, x+pat[2][0], y+pat[2][1]) != None) or \ 311 | (getGemAt(board, x+pat[0][1], y+pat[0][0]) == \ 312 | getGemAt(board, x+pat[1][1], y+pat[1][0]) == \ 313 | getGemAt(board, x+pat[2][1], y+pat[2][0]) != None): 314 | return True # return True the first time you find a pattern 315 | return False 316 | 317 | 318 | def drawMovingGem(gem, progress): 319 | # Draw a gem sliding in the direction that its 'direction' key 320 | # indicates. The progress parameter is a number from 0 (just 321 | # starting) to 100 (slide complete). 322 | movex = 0 323 | movey = 0 324 | progress *= 0.01 325 | 326 | if gem['direction'] == UP: 327 | movey = -int(progress * GEMIMAGESIZE) 328 | elif gem['direction'] == DOWN: 329 | movey = int(progress * GEMIMAGESIZE) 330 | elif gem['direction'] == RIGHT: 331 | movex = int(progress * GEMIMAGESIZE) 332 | elif gem['direction'] == LEFT: 333 | movex = -int(progress * GEMIMAGESIZE) 334 | 335 | basex = gem['x'] 336 | basey = gem['y'] 337 | if basey == ROWABOVEBOARD: 338 | basey = -1 339 | 340 | pixelx = XMARGIN + (basex * GEMIMAGESIZE) 341 | pixely = YMARGIN + (basey * GEMIMAGESIZE) 342 | r = pygame.Rect( (pixelx + movex, pixely + movey, GEMIMAGESIZE, GEMIMAGESIZE) ) 343 | DISPLAYSURF.blit(GEMIMAGES[gem['imageNum']], r) 344 | 345 | 346 | def pullDownAllGems(board): 347 | # pulls down gems on the board to the bottom to fill in any gaps 348 | for x in range(BOARDWIDTH): 349 | gemsInColumn = [] 350 | for y in range(BOARDHEIGHT): 351 | if board[x][y] != EMPTY_SPACE: 352 | gemsInColumn.append(board[x][y]) 353 | board[x] = ([EMPTY_SPACE] * (BOARDHEIGHT - len(gemsInColumn))) + gemsInColumn 354 | 355 | 356 | def getGemAt(board, x, y): 357 | if x < 0 or y < 0 or x >= BOARDWIDTH or y >= BOARDHEIGHT: 358 | return None 359 | else: 360 | return board[x][y] 361 | 362 | 363 | def getDropSlots(board): 364 | # Creates a "drop slot" for each column and fills the slot with a 365 | # number of gems that that column is lacking. This function assumes 366 | # that the gems have been gravity dropped already. 367 | boardCopy = copy.deepcopy(board) 368 | pullDownAllGems(boardCopy) 369 | 370 | dropSlots = [] 371 | for i in range(BOARDWIDTH): 372 | dropSlots.append([]) 373 | 374 | # count the number of empty spaces in each column on the board 375 | for x in range(BOARDWIDTH): 376 | for y in range(BOARDHEIGHT-1, -1, -1): # start from bottom, going up 377 | if boardCopy[x][y] == EMPTY_SPACE: 378 | possibleGems = list(range(len(GEMIMAGES))) 379 | for offsetX, offsetY in ((0, -1), (1, 0), (0, 1), (-1, 0)): 380 | # Narrow down the possible gems we should put in the 381 | # blank space so we don't end up putting an two of 382 | # the same gems next to each other when they drop. 383 | neighborGem = getGemAt(boardCopy, x + offsetX, y + offsetY) 384 | if neighborGem != None and neighborGem in possibleGems: 385 | possibleGems.remove(neighborGem) 386 | 387 | newGem = random.choice(possibleGems) 388 | boardCopy[x][y] = newGem 389 | dropSlots[x].append(newGem) 390 | return dropSlots 391 | 392 | 393 | def findMatchingGems(board): 394 | gemsToRemove = [] # a list of lists of gems in matching triplets that should be removed 395 | boardCopy = copy.deepcopy(board) 396 | 397 | # loop through each space, checking for 3 adjacent identical gems 398 | for x in range(BOARDWIDTH): 399 | for y in range(BOARDHEIGHT): 400 | # look for horizontal matches 401 | if getGemAt(boardCopy, x, y) == getGemAt(boardCopy, x + 1, y) == getGemAt(boardCopy, x + 2, y) and getGemAt(boardCopy, x, y) != EMPTY_SPACE: 402 | targetGem = boardCopy[x][y] 403 | offset = 0 404 | removeSet = [] 405 | while getGemAt(boardCopy, x + offset, y) == targetGem: 406 | # keep checking if there's more than 3 gems in a row 407 | removeSet.append((x + offset, y)) 408 | boardCopy[x + offset][y] = EMPTY_SPACE 409 | offset += 1 410 | gemsToRemove.append(removeSet) 411 | 412 | # look for vertical matches 413 | if getGemAt(boardCopy, x, y) == getGemAt(boardCopy, x, y + 1) == getGemAt(boardCopy, x, y + 2) and getGemAt(boardCopy, x, y) != EMPTY_SPACE: 414 | targetGem = boardCopy[x][y] 415 | offset = 0 416 | removeSet = [] 417 | while getGemAt(boardCopy, x, y + offset) == targetGem: 418 | # keep checking, in case there's more than 3 gems in a row 419 | removeSet.append((x, y + offset)) 420 | boardCopy[x][y + offset] = EMPTY_SPACE 421 | offset += 1 422 | gemsToRemove.append(removeSet) 423 | 424 | return gemsToRemove 425 | 426 | 427 | def highlightSpace(x, y): 428 | pygame.draw.rect(DISPLAYSURF, HIGHLIGHTCOLOR, BOARDRECTS[x][y], 4) 429 | 430 | 431 | def getDroppingGems(board): 432 | # Find all the gems that have an empty space below them 433 | boardCopy = copy.deepcopy(board) 434 | droppingGems = [] 435 | for x in range(BOARDWIDTH): 436 | for y in range(BOARDHEIGHT - 2, -1, -1): 437 | if boardCopy[x][y + 1] == EMPTY_SPACE and boardCopy[x][y] != EMPTY_SPACE: 438 | # This space drops if not empty but the space below it is 439 | droppingGems.append( {'imageNum': boardCopy[x][y], 'x': x, 'y': y, 'direction': DOWN} ) 440 | boardCopy[x][y] = EMPTY_SPACE 441 | return droppingGems 442 | 443 | 444 | def animateMovingGems(board, gems, pointsText, score): 445 | # pointsText is a dictionary with keys 'x', 'y', and 'points' 446 | progress = 0 # progress at 0 represents beginning, 100 means finished. 447 | while progress < 100: # animation loop 448 | DISPLAYSURF.fill(BGCOLOR) 449 | drawBoard(board) 450 | for gem in gems: # Draw each gem. 451 | drawMovingGem(gem, progress) 452 | drawScore(score) 453 | for pointText in pointsText: 454 | pointsSurf = BASICFONT.render(str(pointText['points']), 1, SCORECOLOR) 455 | pointsRect = pointsSurf.get_rect() 456 | pointsRect.center = (pointText['x'], pointText['y']) 457 | DISPLAYSURF.blit(pointsSurf, pointsRect) 458 | 459 | pygame.display.update() 460 | FPSCLOCK.tick(FPS) 461 | progress += MOVERATE # progress the animation a little bit more for the next frame 462 | 463 | 464 | def moveGems(board, movingGems): 465 | # movingGems is a list of dicts with keys x, y, direction, imageNum 466 | for gem in movingGems: 467 | if gem['y'] != ROWABOVEBOARD: 468 | board[gem['x']][gem['y']] = EMPTY_SPACE 469 | movex = 0 470 | movey = 0 471 | if gem['direction'] == LEFT: 472 | movex = -1 473 | elif gem['direction'] == RIGHT: 474 | movex = 1 475 | elif gem['direction'] == DOWN: 476 | movey = 1 477 | elif gem['direction'] == UP: 478 | movey = -1 479 | board[gem['x'] + movex][gem['y'] + movey] = gem['imageNum'] 480 | else: 481 | # gem is located above the board (where new gems come from) 482 | board[gem['x']][0] = gem['imageNum'] # move to top row 483 | 484 | 485 | def fillBoardAndAnimate(board, points, score): 486 | dropSlots = getDropSlots(board) 487 | while dropSlots != [[]] * BOARDWIDTH: 488 | # do the dropping animation as long as there are more gems to drop 489 | movingGems = getDroppingGems(board) 490 | for x in range(len(dropSlots)): 491 | if len(dropSlots[x]) != 0: 492 | # cause the lowest gem in each slot to begin moving in the DOWN direction 493 | movingGems.append({'imageNum': dropSlots[x][0], 'x': x, 'y': ROWABOVEBOARD, 'direction': DOWN}) 494 | 495 | boardCopy = getBoardCopyMinusGems(board, movingGems) 496 | animateMovingGems(boardCopy, movingGems, points, score) 497 | moveGems(board, movingGems) 498 | 499 | # Make the next row of gems from the drop slots 500 | # the lowest by deleting the previous lowest gems. 501 | for x in range(len(dropSlots)): 502 | if len(dropSlots[x]) == 0: 503 | continue 504 | board[x][0] = dropSlots[x][0] 505 | del dropSlots[x][0] 506 | 507 | 508 | def checkForGemClick(pos): 509 | # See if the mouse click was on the board 510 | for x in range(BOARDWIDTH): 511 | for y in range(BOARDHEIGHT): 512 | if BOARDRECTS[x][y].collidepoint(pos[0], pos[1]): 513 | return {'x': x, 'y': y} 514 | return None # Click was not on the board. 515 | 516 | 517 | def drawBoard(board): 518 | for x in range(BOARDWIDTH): 519 | for y in range(BOARDHEIGHT): 520 | pygame.draw.rect(DISPLAYSURF, GRIDCOLOR, BOARDRECTS[x][y], 1) 521 | gemToDraw = board[x][y] 522 | if gemToDraw != EMPTY_SPACE: 523 | DISPLAYSURF.blit(GEMIMAGES[gemToDraw], BOARDRECTS[x][y]) 524 | 525 | 526 | def getBoardCopyMinusGems(board, gems): 527 | # Creates and returns a copy of the passed board data structure, 528 | # with the gems in the "gems" list removed from it. 529 | # 530 | # Gems is a list of dicts, with keys x, y, direction, imageNum 531 | 532 | boardCopy = copy.deepcopy(board) 533 | 534 | # Remove some of the gems from this board data structure copy. 535 | for gem in gems: 536 | if gem['y'] != ROWABOVEBOARD: 537 | boardCopy[gem['x']][gem['y']] = EMPTY_SPACE 538 | return boardCopy 539 | 540 | 541 | def drawScore(score): 542 | scoreImg = BASICFONT.render(str(score), 1, SCORECOLOR) 543 | scoreRect = scoreImg.get_rect() 544 | scoreRect.bottomleft = (10, WINDOWHEIGHT - 6) 545 | DISPLAYSURF.blit(scoreImg, scoreRect) 546 | 547 | 548 | if __name__ == '__main__': 549 | main() 550 | -------------------------------------------------------------------------------- /starpusher/starpusher.py: -------------------------------------------------------------------------------- 1 | # Star Pusher (a Sokoban clone) 2 | # By Al Sweigart al@inventwithpython.com 3 | # http://inventwithpython.com/pygame 4 | # Released under a "Simplified BSD" license 5 | 6 | import random, sys, copy, os, pygame 7 | from pygame.locals import * 8 | 9 | FPS = 30 # frames per second to update the screen 10 | WINWIDTH = 800 # width of the program's window, in pixels 11 | WINHEIGHT = 600 # height in pixels 12 | HALF_WINWIDTH = int(WINWIDTH / 2) 13 | HALF_WINHEIGHT = int(WINHEIGHT / 2) 14 | 15 | # The total width and height of each tile in pixels. 16 | TILEWIDTH = 50 17 | TILEHEIGHT = 85 18 | TILEFLOORHEIGHT = 40 19 | 20 | CAM_MOVE_SPEED = 5 # how many pixels per frame the camera moves 21 | 22 | # The percentage of outdoor tiles that have additional 23 | # decoration on them, such as a tree or rock. 24 | OUTSIDE_DECORATION_PCT = 20 25 | 26 | BRIGHTBLUE = ( 0, 170, 255) 27 | WHITE = (255, 255, 255) 28 | BGCOLOR = BRIGHTBLUE 29 | TEXTCOLOR = WHITE 30 | 31 | UP = 'up' 32 | DOWN = 'down' 33 | LEFT = 'left' 34 | RIGHT = 'right' 35 | 36 | 37 | def main(): 38 | global FPSCLOCK, DISPLAYSURF, IMAGESDICT, TILEMAPPING, OUTSIDEDECOMAPPING, BASICFONT, PLAYERIMAGES, currentImage 39 | 40 | # Pygame initialization and basic set up of the global variables. 41 | pygame.init() 42 | FPSCLOCK = pygame.time.Clock() 43 | 44 | # Because the Surface object stored in DISPLAYSURF was returned 45 | # from the pygame.display.set_mode() function, this is the 46 | # Surface object that is drawn to the actual computer screen 47 | # when pygame.display.update() is called. 48 | DISPLAYSURF = pygame.display.set_mode((WINWIDTH, WINHEIGHT)) 49 | 50 | pygame.display.set_caption('Star Pusher') 51 | BASICFONT = pygame.font.Font('freesansbold.ttf', 18) 52 | 53 | # A global dict value that will contain all the Pygame 54 | # Surface objects returned by pygame.image.load(). 55 | IMAGESDICT = {'uncovered goal': pygame.image.load('RedSelector.png'), 56 | 'covered goal': pygame.image.load('Selector.png'), 57 | 'star': pygame.image.load('Star.png'), 58 | 'corner': pygame.image.load('Wall_Block_Tall.png'), 59 | 'wall': pygame.image.load('Wood_Block_Tall.png'), 60 | 'inside floor': pygame.image.load('Plain_Block.png'), 61 | 'outside floor': pygame.image.load('Grass_Block.png'), 62 | 'title': pygame.image.load('star_title.png'), 63 | 'solved': pygame.image.load('star_solved.png'), 64 | 'princess': pygame.image.load('princess.png'), 65 | 'boy': pygame.image.load('boy.png'), 66 | 'catgirl': pygame.image.load('catgirl.png'), 67 | 'horngirl': pygame.image.load('horngirl.png'), 68 | 'pinkgirl': pygame.image.load('pinkgirl.png'), 69 | 'rock': pygame.image.load('Rock.png'), 70 | 'short tree': pygame.image.load('Tree_Short.png'), 71 | 'tall tree': pygame.image.load('Tree_Tall.png'), 72 | 'ugly tree': pygame.image.load('Tree_Ugly.png')} 73 | 74 | # These dict values are global, and map the character that appears 75 | # in the level file to the Surface object it represents. 76 | TILEMAPPING = {'x': IMAGESDICT['corner'], 77 | '#': IMAGESDICT['wall'], 78 | 'o': IMAGESDICT['inside floor'], 79 | ' ': IMAGESDICT['outside floor']} 80 | OUTSIDEDECOMAPPING = {'1': IMAGESDICT['rock'], 81 | '2': IMAGESDICT['short tree'], 82 | '3': IMAGESDICT['tall tree'], 83 | '4': IMAGESDICT['ugly tree']} 84 | 85 | # PLAYERIMAGES is a list of all possible characters the player can be. 86 | # currentImage is the index of the player's current player image. 87 | currentImage = 0 88 | PLAYERIMAGES = [IMAGESDICT['princess'], 89 | IMAGESDICT['boy'], 90 | IMAGESDICT['catgirl'], 91 | IMAGESDICT['horngirl'], 92 | IMAGESDICT['pinkgirl']] 93 | 94 | startScreen() # show the title screen until the user presses a key 95 | 96 | # Read in the levels from the text file. See the readLevelsFile() for 97 | # details on the format of this file and how to make your own levels. 98 | levels = readLevelsFile('starPusherLevels.txt') 99 | currentLevelIndex = 0 100 | 101 | # The main game loop. This loop runs a single level, when the user 102 | # finishes that level, the next/previous level is loaded. 103 | while True: # main game loop 104 | # Run the level to actually start playing the game: 105 | result = runLevel(levels, currentLevelIndex) 106 | 107 | if result in ('solved', 'next'): 108 | # Go to the next level. 109 | currentLevelIndex += 1 110 | if currentLevelIndex >= len(levels): 111 | # If there are no more levels, go back to the first one. 112 | currentLevelIndex = 0 113 | elif result == 'back': 114 | # Go to the previous level. 115 | currentLevelIndex -= 1 116 | if currentLevelIndex < 0: 117 | # If there are no previous levels, go to the last one. 118 | currentLevelIndex = len(levels)-1 119 | elif result == 'reset': 120 | pass # Do nothing. Loop re-calls runLevel() to reset the level 121 | 122 | 123 | def runLevel(levels, levelNum): 124 | global currentImage 125 | levelObj = levels[levelNum] 126 | mapObj = decorateMap(levelObj['mapObj'], levelObj['startState']['player']) 127 | gameStateObj = copy.deepcopy(levelObj['startState']) 128 | mapNeedsRedraw = True # set to True to call drawMap() 129 | levelSurf = BASICFONT.render('Level %s of %s' % (levelNum + 1, len(levels)), 1, TEXTCOLOR) 130 | levelRect = levelSurf.get_rect() 131 | levelRect.bottomleft = (20, WINHEIGHT - 35) 132 | mapWidth = len(mapObj) * TILEWIDTH 133 | mapHeight = (len(mapObj[0]) - 1) * TILEFLOORHEIGHT + TILEHEIGHT 134 | MAX_CAM_X_PAN = abs(HALF_WINHEIGHT - int(mapHeight / 2)) + TILEWIDTH 135 | MAX_CAM_Y_PAN = abs(HALF_WINWIDTH - int(mapWidth / 2)) + TILEHEIGHT 136 | 137 | levelIsComplete = False 138 | # Track how much the camera has moved: 139 | cameraOffsetX = 0 140 | cameraOffsetY = 0 141 | # Track if the keys to move the camera are being held down: 142 | cameraUp = False 143 | cameraDown = False 144 | cameraLeft = False 145 | cameraRight = False 146 | 147 | while True: # main game loop 148 | # Reset these variables: 149 | playerMoveTo = None 150 | keyPressed = False 151 | 152 | for event in pygame.event.get(): # event handling loop 153 | if event.type == QUIT: 154 | # Player clicked the "X" at the corner of the window. 155 | terminate() 156 | 157 | elif event.type == KEYDOWN: 158 | # Handle key presses 159 | keyPressed = True 160 | if event.key == K_LEFT: 161 | playerMoveTo = LEFT 162 | elif event.key == K_RIGHT: 163 | playerMoveTo = RIGHT 164 | elif event.key == K_UP: 165 | playerMoveTo = UP 166 | elif event.key == K_DOWN: 167 | playerMoveTo = DOWN 168 | 169 | # Set the camera move mode. 170 | elif event.key == K_a: 171 | cameraLeft = True 172 | elif event.key == K_d: 173 | cameraRight = True 174 | elif event.key == K_w: 175 | cameraUp = True 176 | elif event.key == K_s: 177 | cameraDown = True 178 | 179 | elif event.key == K_n: 180 | return 'next' 181 | elif event.key == K_b: 182 | return 'back' 183 | 184 | elif event.key == K_ESCAPE: 185 | terminate() # Esc key quits. 186 | elif event.key == K_BACKSPACE: 187 | return 'reset' # Reset the level. 188 | elif event.key == K_p: 189 | # Change the player image to the next one. 190 | currentImage += 1 191 | if currentImage >= len(PLAYERIMAGES): 192 | # After the last player image, use the first one. 193 | currentImage = 0 194 | mapNeedsRedraw = True 195 | 196 | elif event.type == KEYUP: 197 | # Unset the camera move mode. 198 | if event.key == K_a: 199 | cameraLeft = False 200 | elif event.key == K_d: 201 | cameraRight = False 202 | elif event.key == K_w: 203 | cameraUp = False 204 | elif event.key == K_s: 205 | cameraDown = False 206 | 207 | if playerMoveTo != None and not levelIsComplete: 208 | # If the player pushed a key to move, make the move 209 | # (if possible) and push any stars that are pushable. 210 | moved = makeMove(mapObj, gameStateObj, playerMoveTo) 211 | 212 | if moved: 213 | # increment the step counter. 214 | gameStateObj['stepCounter'] += 1 215 | mapNeedsRedraw = True 216 | 217 | if isLevelFinished(levelObj, gameStateObj): 218 | # level is solved, we should show the "Solved!" image. 219 | levelIsComplete = True 220 | keyPressed = False 221 | 222 | DISPLAYSURF.fill(BGCOLOR) 223 | 224 | if mapNeedsRedraw: 225 | mapSurf = drawMap(mapObj, gameStateObj, levelObj['goals']) 226 | mapNeedsRedraw = False 227 | 228 | if cameraUp and cameraOffsetY < MAX_CAM_X_PAN: 229 | cameraOffsetY += CAM_MOVE_SPEED 230 | elif cameraDown and cameraOffsetY > -MAX_CAM_X_PAN: 231 | cameraOffsetY -= CAM_MOVE_SPEED 232 | if cameraLeft and cameraOffsetX < MAX_CAM_Y_PAN: 233 | cameraOffsetX += CAM_MOVE_SPEED 234 | elif cameraRight and cameraOffsetX > -MAX_CAM_Y_PAN: 235 | cameraOffsetX -= CAM_MOVE_SPEED 236 | 237 | # Adjust mapSurf's Rect object based on the camera offset. 238 | mapSurfRect = mapSurf.get_rect() 239 | mapSurfRect.center = (HALF_WINWIDTH + cameraOffsetX, HALF_WINHEIGHT + cameraOffsetY) 240 | 241 | # Draw mapSurf to the DISPLAYSURF Surface object. 242 | DISPLAYSURF.blit(mapSurf, mapSurfRect) 243 | 244 | DISPLAYSURF.blit(levelSurf, levelRect) 245 | stepSurf = BASICFONT.render('Steps: %s' % (gameStateObj['stepCounter']), 1, TEXTCOLOR) 246 | stepRect = stepSurf.get_rect() 247 | stepRect.bottomleft = (20, WINHEIGHT - 10) 248 | DISPLAYSURF.blit(stepSurf, stepRect) 249 | 250 | if levelIsComplete: 251 | # is solved, show the "Solved!" image until the player 252 | # has pressed a key. 253 | solvedRect = IMAGESDICT['solved'].get_rect() 254 | solvedRect.center = (HALF_WINWIDTH, HALF_WINHEIGHT) 255 | DISPLAYSURF.blit(IMAGESDICT['solved'], solvedRect) 256 | 257 | if keyPressed: 258 | return 'solved' 259 | 260 | pygame.display.update() # draw DISPLAYSURF to the screen. 261 | FPSCLOCK.tick() 262 | 263 | 264 | def isWall(mapObj, x, y): 265 | """Returns True if the (x, y) position on 266 | the map is a wall, otherwise return False.""" 267 | if x < 0 or x >= len(mapObj) or y < 0 or y >= len(mapObj[x]): 268 | return False # x and y aren't actually on the map. 269 | elif mapObj[x][y] in ('#', 'x'): 270 | return True # wall is blocking 271 | return False 272 | 273 | 274 | def decorateMap(mapObj, startxy): 275 | """Makes a copy of the given map object and modifies it. 276 | Here is what is done to it: 277 | * Walls that are corners are turned into corner pieces. 278 | * The outside/inside floor tile distinction is made. 279 | * Tree/rock decorations are randomly added to the outside tiles. 280 | 281 | Returns the decorated map object.""" 282 | 283 | startx, starty = startxy # Syntactic sugar 284 | 285 | # Copy the map object so we don't modify the original passed 286 | mapObjCopy = copy.deepcopy(mapObj) 287 | 288 | # Remove the non-wall characters from the map data 289 | for x in range(len(mapObjCopy)): 290 | for y in range(len(mapObjCopy[0])): 291 | if mapObjCopy[x][y] in ('$', '.', '@', '+', '*'): 292 | mapObjCopy[x][y] = ' ' 293 | 294 | # Flood fill to determine inside/outside floor tiles. 295 | floodFill(mapObjCopy, startx, starty, ' ', 'o') 296 | 297 | # Convert the adjoined walls into corner tiles. 298 | for x in range(len(mapObjCopy)): 299 | for y in range(len(mapObjCopy[0])): 300 | 301 | if mapObjCopy[x][y] == '#': 302 | if (isWall(mapObjCopy, x, y-1) and isWall(mapObjCopy, x+1, y)) or \ 303 | (isWall(mapObjCopy, x+1, y) and isWall(mapObjCopy, x, y+1)) or \ 304 | (isWall(mapObjCopy, x, y+1) and isWall(mapObjCopy, x-1, y)) or \ 305 | (isWall(mapObjCopy, x-1, y) and isWall(mapObjCopy, x, y-1)): 306 | mapObjCopy[x][y] = 'x' 307 | 308 | elif mapObjCopy[x][y] == ' ' and random.randint(0, 99) < OUTSIDE_DECORATION_PCT: 309 | mapObjCopy[x][y] = random.choice(list(OUTSIDEDECOMAPPING.keys())) 310 | 311 | return mapObjCopy 312 | 313 | 314 | def isBlocked(mapObj, gameStateObj, x, y): 315 | """Returns True if the (x, y) position on the map is 316 | blocked by a wall or star, otherwise return False.""" 317 | 318 | if isWall(mapObj, x, y): 319 | return True 320 | 321 | elif x < 0 or x >= len(mapObj) or y < 0 or y >= len(mapObj[x]): 322 | return True # x and y aren't actually on the map. 323 | 324 | elif (x, y) in gameStateObj['stars']: 325 | return True # a star is blocking 326 | 327 | return False 328 | 329 | 330 | def makeMove(mapObj, gameStateObj, playerMoveTo): 331 | """Given a map and game state object, see if it is possible for the 332 | player to make the given move. If it is, then change the player's 333 | position (and the position of any pushed star). If not, do nothing. 334 | 335 | Returns True if the player moved, otherwise False.""" 336 | 337 | # Make sure the player can move in the direction they want. 338 | playerx, playery = gameStateObj['player'] 339 | 340 | # This variable is "syntactic sugar". Typing "stars" is more 341 | # readable than typing "gameStateObj['stars']" in our code. 342 | stars = gameStateObj['stars'] 343 | 344 | # The code for handling each of the directions is so similar aside 345 | # from adding or subtracting 1 to the x/y coordinates. We can 346 | # simplify it by using the xOffset and yOffset variables. 347 | if playerMoveTo == UP: 348 | xOffset = 0 349 | yOffset = -1 350 | elif playerMoveTo == RIGHT: 351 | xOffset = 1 352 | yOffset = 0 353 | elif playerMoveTo == DOWN: 354 | xOffset = 0 355 | yOffset = 1 356 | elif playerMoveTo == LEFT: 357 | xOffset = -1 358 | yOffset = 0 359 | 360 | # See if the player can move in that direction. 361 | if isWall(mapObj, playerx + xOffset, playery + yOffset): 362 | return False 363 | else: 364 | if (playerx + xOffset, playery + yOffset) in stars: 365 | # There is a star in the way, see if the player can push it. 366 | if not isBlocked(mapObj, gameStateObj, playerx + (xOffset*2), playery + (yOffset*2)): 367 | # Move the star. 368 | ind = stars.index((playerx + xOffset, playery + yOffset)) 369 | stars[ind] = (stars[ind][0] + xOffset, stars[ind][1] + yOffset) 370 | else: 371 | return False 372 | # Move the player upwards. 373 | gameStateObj['player'] = (playerx + xOffset, playery + yOffset) 374 | return True 375 | 376 | 377 | def startScreen(): 378 | """Display the start screen (which has the title and instructions) 379 | until the player presses a key. Returns None.""" 380 | 381 | # Position the title image. 382 | titleRect = IMAGESDICT['title'].get_rect() 383 | topCoord = 50 # topCoord tracks where to position the top of the text 384 | titleRect.top = topCoord 385 | titleRect.centerx = HALF_WINWIDTH 386 | topCoord += titleRect.height 387 | 388 | # Unfortunately, Pygame's font & text system only shows one line at 389 | # a time, so we can't use strings with \n newline characters in them. 390 | # So we will use a list with each line in it. 391 | instructionText = ['Push the stars over the marks.', 392 | 'Arrow keys to move, WASD for camera control, P to change character.', 393 | 'Backspace to reset level, Esc to quit.', 394 | 'N for next level, B to go back a level.'] 395 | 396 | # Start with drawing a blank color to the entire window: 397 | DISPLAYSURF.fill(BGCOLOR) 398 | 399 | # Draw the title image to the window: 400 | DISPLAYSURF.blit(IMAGESDICT['title'], titleRect) 401 | 402 | # Position and draw the text. 403 | for i in range(len(instructionText)): 404 | instSurf = BASICFONT.render(instructionText[i], 1, TEXTCOLOR) 405 | instRect = instSurf.get_rect() 406 | topCoord += 10 # 10 pixels will go in between each line of text. 407 | instRect.top = topCoord 408 | instRect.centerx = HALF_WINWIDTH 409 | topCoord += instRect.height # Adjust for the height of the line. 410 | DISPLAYSURF.blit(instSurf, instRect) 411 | 412 | while True: # Main loop for the start screen. 413 | for event in pygame.event.get(): 414 | if event.type == QUIT: 415 | terminate() 416 | elif event.type == KEYDOWN: 417 | if event.key == K_ESCAPE: 418 | terminate() 419 | return # user has pressed a key, so return. 420 | 421 | # Display the DISPLAYSURF contents to the actual screen. 422 | pygame.display.update() 423 | FPSCLOCK.tick() 424 | 425 | 426 | def readLevelsFile(filename): 427 | assert os.path.exists(filename), 'Cannot find the level file: %s' % (filename) 428 | mapFile = open(filename, 'r') 429 | # Each level must end with a blank line 430 | content = mapFile.readlines() + ['\r\n'] 431 | mapFile.close() 432 | 433 | levels = [] # Will contain a list of level objects. 434 | levelNum = 0 435 | mapTextLines = [] # contains the lines for a single level's map. 436 | mapObj = [] # the map object made from the data in mapTextLines 437 | for lineNum in range(len(content)): 438 | # Process each line that was in the level file. 439 | line = content[lineNum].rstrip('\r\n') 440 | 441 | if ';' in line: 442 | # Ignore the ; lines, they're comments in the level file. 443 | line = line[:line.find(';')] 444 | 445 | if line != '': 446 | # This line is part of the map. 447 | mapTextLines.append(line) 448 | elif line == '' and len(mapTextLines) > 0: 449 | # A blank line indicates the end of a level's map in the file. 450 | # Convert the text in mapTextLines into a level object. 451 | 452 | # Find the longest row in the map. 453 | maxWidth = -1 454 | for i in range(len(mapTextLines)): 455 | if len(mapTextLines[i]) > maxWidth: 456 | maxWidth = len(mapTextLines[i]) 457 | # Add spaces to the ends of the shorter rows. This 458 | # ensures the map will be rectangular. 459 | for i in range(len(mapTextLines)): 460 | mapTextLines[i] += ' ' * (maxWidth - len(mapTextLines[i])) 461 | 462 | # Convert mapTextLines to a map object. 463 | for x in range(len(mapTextLines[0])): 464 | mapObj.append([]) 465 | for y in range(len(mapTextLines)): 466 | for x in range(maxWidth): 467 | mapObj[x].append(mapTextLines[y][x]) 468 | 469 | # Loop through the spaces in the map and find the @, ., and $ 470 | # characters for the starting game state. 471 | startx = None # The x and y for the player's starting position 472 | starty = None 473 | goals = [] # list of (x, y) tuples for each goal. 474 | stars = [] # list of (x, y) for each star's starting position. 475 | for x in range(maxWidth): 476 | for y in range(len(mapObj[x])): 477 | if mapObj[x][y] in ('@', '+'): 478 | # '@' is player, '+' is player & goal 479 | startx = x 480 | starty = y 481 | if mapObj[x][y] in ('.', '+', '*'): 482 | # '.' is goal, '*' is star & goal 483 | goals.append((x, y)) 484 | if mapObj[x][y] in ('$', '*'): 485 | # '$' is star 486 | stars.append((x, y)) 487 | 488 | # Basic level design sanity checks: 489 | assert startx != None and starty != None, 'Level %s (around line %s) in %s is missing a "@" or "+" to mark the start point.' % (levelNum+1, lineNum, filename) 490 | assert len(goals) > 0, 'Level %s (around line %s) in %s must have at least one goal.' % (levelNum+1, lineNum, filename) 491 | assert len(stars) >= len(goals), 'Level %s (around line %s) in %s is impossible to solve. It has %s goals but only %s stars.' % (levelNum+1, lineNum, filename, len(goals), len(stars)) 492 | 493 | # Create level object and starting game state object. 494 | gameStateObj = {'player': (startx, starty), 495 | 'stepCounter': 0, 496 | 'stars': stars} 497 | levelObj = {'width': maxWidth, 498 | 'height': len(mapObj), 499 | 'mapObj': mapObj, 500 | 'goals': goals, 501 | 'startState': gameStateObj} 502 | 503 | levels.append(levelObj) 504 | 505 | # Reset the variables for reading the next map. 506 | mapTextLines = [] 507 | mapObj = [] 508 | gameStateObj = {} 509 | levelNum += 1 510 | return levels 511 | 512 | 513 | def floodFill(mapObj, x, y, oldCharacter, newCharacter): 514 | """Changes any values matching oldCharacter on the map object to 515 | newCharacter at the (x, y) position, and does the same for the 516 | positions to the left, right, down, and up of (x, y), recursively.""" 517 | 518 | # In this game, the flood fill algorithm creates the inside/outside 519 | # floor distinction. This is a "recursive" function. 520 | # For more info on the Flood Fill algorithm, see: 521 | # http://en.wikipedia.org/wiki/Flood_fill 522 | if mapObj[x][y] == oldCharacter: 523 | mapObj[x][y] = newCharacter 524 | 525 | if x < len(mapObj) - 1 and mapObj[x+1][y] == oldCharacter: 526 | floodFill(mapObj, x+1, y, oldCharacter, newCharacter) # call right 527 | if x > 0 and mapObj[x-1][y] == oldCharacter: 528 | floodFill(mapObj, x-1, y, oldCharacter, newCharacter) # call left 529 | if y < len(mapObj[x]) - 1 and mapObj[x][y+1] == oldCharacter: 530 | floodFill(mapObj, x, y+1, oldCharacter, newCharacter) # call down 531 | if y > 0 and mapObj[x][y-1] == oldCharacter: 532 | floodFill(mapObj, x, y-1, oldCharacter, newCharacter) # call up 533 | 534 | 535 | def drawMap(mapObj, gameStateObj, goals): 536 | """Draws the map to a Surface object, including the player and 537 | stars. This function does not call pygame.display.update(), nor 538 | does it draw the "Level" and "Steps" text in the corner.""" 539 | 540 | # mapSurf will be the single Surface object that the tiles are drawn 541 | # on, so that it is easy to position the entire map on the DISPLAYSURF 542 | # Surface object. First, the width and height must be calculated. 543 | mapSurfWidth = len(mapObj) * TILEWIDTH 544 | mapSurfHeight = (len(mapObj[0]) - 1) * TILEFLOORHEIGHT + TILEHEIGHT 545 | mapSurf = pygame.Surface((mapSurfWidth, mapSurfHeight)) 546 | mapSurf.fill(BGCOLOR) # start with a blank color on the surface. 547 | 548 | # Draw the tile sprites onto this surface. 549 | for x in range(len(mapObj)): 550 | for y in range(len(mapObj[x])): 551 | spaceRect = pygame.Rect((x * TILEWIDTH, y * TILEFLOORHEIGHT, TILEWIDTH, TILEHEIGHT)) 552 | if mapObj[x][y] in TILEMAPPING: 553 | baseTile = TILEMAPPING[mapObj[x][y]] 554 | elif mapObj[x][y] in OUTSIDEDECOMAPPING: 555 | baseTile = TILEMAPPING[' '] 556 | 557 | # First draw the base ground/wall tile. 558 | mapSurf.blit(baseTile, spaceRect) 559 | 560 | if mapObj[x][y] in OUTSIDEDECOMAPPING: 561 | # Draw any tree/rock decorations that are on this tile. 562 | mapSurf.blit(OUTSIDEDECOMAPPING[mapObj[x][y]], spaceRect) 563 | elif (x, y) in gameStateObj['stars']: 564 | if (x, y) in goals: 565 | # A goal AND star are on this space, draw goal first. 566 | mapSurf.blit(IMAGESDICT['covered goal'], spaceRect) 567 | # Then draw the star sprite. 568 | mapSurf.blit(IMAGESDICT['star'], spaceRect) 569 | elif (x, y) in goals: 570 | # Draw a goal without a star on it. 571 | mapSurf.blit(IMAGESDICT['uncovered goal'], spaceRect) 572 | 573 | # Last draw the player on the board. 574 | if (x, y) == gameStateObj['player']: 575 | # Note: The value "currentImage" refers 576 | # to a key in "PLAYERIMAGES" which has the 577 | # specific player image we want to show. 578 | mapSurf.blit(PLAYERIMAGES[currentImage], spaceRect) 579 | 580 | return mapSurf 581 | 582 | 583 | def isLevelFinished(levelObj, gameStateObj): 584 | """Returns True if all the goals have stars in them.""" 585 | for goal in levelObj['goals']: 586 | if goal not in gameStateObj['stars']: 587 | # Found a space with a goal but no star on it. 588 | return False 589 | return True 590 | 591 | 592 | def terminate(): 593 | pygame.quit() 594 | sys.exit() 595 | 596 | 597 | if __name__ == '__main__': 598 | main() --------------------------------------------------------------------------------