| Uutiset | Koodikirjasto | Wiki | Keskustelut | FAQ | Info |
Moninpelattava Tetris Pythonillaempty 17.04.03 16:29 Tekstipohjainen Python-Tetris jossa voi olla 1-3 pelaajaa
# 1-3 pelaajan Tetris Pythonilla. Ominaisuuksina mm. # pistelaskuri, palkkien nopea tiputtaminen, pyörittäminen # kumpaankin suuntaan, vihollispelaajien rankaisu kun rivejä # saa tuhottua, eri väriset palikat sekä seuraavan palikan näyttäminen. # # Ajamiseen tarvitset Console-moduulin, jonka saa täältä # http://effbot.org/zone/console-handbook.htm # # Tai tarkemmin ottaen täältä: # http://effbot.org/downloads/ # # Tarvitset myös Python version 2.2 tai uudemman (luulisin). # Testattu Python 2.2.2:lla. # # Yksinpeliä pelataan nuolinäppäimillä + ctrl:lla, kaksinpelissä # on lisäksi w,s,a,d,q ja kolminpelissä i,k,j,l,u. <esc> lopettaa. # # Ohjelma havainnollistaa olioiden hyödyllisyyttä: Moninpelin # lisääminen oli simppeliä kun yhden pelaajan toiminnallisuus oli # eristetty yhteen luokkaan, eikä missään käytetä globaaleja muuttujia. # # Tässä olisi pitänyt käyttää docstringejä dokumentointiin mutta # tämä on kuitenkin tarkoitettu enemmänkin luettavaksi kuin # käytettäväksi, joten käytin näitä peruskommentteja. # # Hannu Kankaanpää import Console import random import time # Block on luokka joka kuvaa yhtä Tetris-palikkaa. # Palkin muoto saadaan parhaiten selville iterate-metodin avulla # (lue ko. metodin ohjeet jotta saat tarkemmat ohjeet) class Block: # Luo uuden palikan. # data -- merkkijonojen taulukko, jossa merkki ' ' tarkoittaa # ettei kohdassa ole palkkia. # Esim. ["# ", "##", "# "] on yksi tetrispalikka. # origo -- palkin keskipiste tuplena (x, y). # Esim. (0, 0) tarkoittaa palkin vasenta ylänurkkaa # Myös float-luvut ovat sallittuja, esim (1.5, 0.5) # color -- palkin väri, 1 - 15 def __init__(self, data, origo, color): # aseta palkin väri self.color = color # blocks on neljän elementin taulukko eri suuntiin # kääntyneistä palkeista self.blocks = [] # Yhden palkkielementin rakenne on seuraava: # block[0] -- palikan "data", eli 2-ulotteinen taulu joka # kertoo missä on palkkia ja missä ei # block[1] -- palikan koko muodossa (leveys, korkeus) # block[2] -- origon paikka muodossa (x, y) self.blocks.append((data, (len(data[0]), len(data)), origo)) # Tee 3 muuta palkkia kääntämällä edellistä palkkia 90 astetta for i in range(1, 4): # prev on edellinen palkki prev = self.blocks[i-1] # Tee uudelle palkille tyhjä "data"-kenttä newblock = [[' ' for i in range(prev[1][1])] for e in range(prev[1][0])] # Kopioi uuteen palkkiin edellinen palkki käännettynä 90 astetta for x in range(prev[1][0]): for y in range(prev[1][1]): newblock[x][-1-y] = prev[0][y][x] # Lisää uusi palkki blocks-taulukkoon. # Leveys saadaan edellisen palkin korkeudesta, # korkeus edellisen palkin leveydestä. # Origon paikka vastaavasti kuin edellä kääntäessä dataa self.blocks.append((["".join(i) for i in newblock], (prev[1][1], prev[1][0]), (prev[1][1] - prev[2][1] - 1, prev[2][0]))) # Palauttaa tiettyyn suuntaan käännetyn palkin # angle on luku 0-3 # Palautetun palkkielementin rakenne on seuraava: # block[0] -- palikan "data", eli 2-ulotteinen taulu joka # kertoo missä on palkkia ja missä ei # block[1] -- palikan koko muodossa (leveys, korkeus) # block[2] -- origon paikka muodossa (x, y) def getRotated(self, angle): return self.blocks[angle] # Tämä funktio käy läpi jokaisen palkin palikan ja kutsuu # kullekin funktiota f. f on funktio joka ottaa kaksi # argumenttia, x:n ja y:n. Nämä vastaavat palkin koordinaatteja def iterate(self, rotation, f): block = self.getRotated(rotation) for x in range(block[1][0]): for y in range(block[1][1]): rx = x - int(block[2][0]) ry = y - int(block[2][1]) if block[0][y][x] != ' ': f(rx, ry) # Pelilauta yhdelle pelaajalle. class Board: # Tässä on määritelty Tetriksen palikat. Selkeyden vuoksi # käytin nimettyjä parametrejä origolle ja color:lle blocks = [Block(["# ", "##", "# "], origo = (0, 1), color = 11), Block(["##", "##"], origo = (0.5, 0.5), color = 3), Block(["#", "#", "#", "#"], origo = (0, 1.5), color = 6), Block(["# ", "# ", "##"], origo = (0.5, 1), color = 5), Block([" #", " #", "##"], origo = (0.5, 1), color = 13), Block([" ##", "## "], origo = (1, 0.5), color = 2), Block(["## ", " ##"], origo = (1, 0.5), color = 10)] # Kuinka paljon saa pisteitä kun tuhoaa eri määrän rivejä? pointIncreases = [1, 4, 10, 30, 120] # Mikä on tyhjän ruudun koodi? emptyBlock = 0 # Luo peliareenan. # console on konsoli mihin piirretään # x, y -- peliareenan sijainti ruudulla # width, height -- peliareenan dimensiot # keys -- 5 merkkijonon taulukko joka kertoo pelaajan napit def __init__(self, console, x, y, width, height, keys): self.x = x self.y = y self.width = width self.height = height self.keys = keys self.console = console # Luo tyhjä peliareena self.area = [[Board.emptyBlock for i in range(width)] for e in range(height)] # Peli ei ole ohi vielä ;) self.gameover = False self.points = 0 # Valitse seuraavaksi palikaksi joku satunnainen palikka self.nextBlock = random.choice(Board.blocks) # Luo ruudulle uusi palikka self.createBlock() # self.enemies on lista vihollisten laudoista self.enemies = [] # Tällä lisäillään viholliset, niin että tiedetään ketä # rankaista kun saadaan rivejä tuhottua. def addEnemy(self, enemyBoard): self.enemies.append(enemyBoard) # Tämä funktio luo uuden palikan, valitsee mikä # on seuraava palikka (self.nextBlock) sekä päivittää # seuraavan palikan kuvaa def createBlock(self): self.block = self.nextBlock self.block_x = self.width / 2 - 1 self.block_y = 0 self.block_rotation = 0 self.block_movewait = 5 self.nextBlock = random.choice(Board.blocks) self.drawNextBlock() # Tätä kutsutaan monta kertaa sekunnissa jotta # pelissä tapahtuisi jotain. keypressed on merkkijono # joka kuvaa viimeksi painettua nappia def move(self, keypressed): # Älä liikuttele mitään jos peli on ohi if self.gameover: return # Ota palkin vanha sijainti ja rotaatio talteen prev_x = self.block_x prev_rotation = self.block_rotation # Tee eri toimintoja eri napeista if keypressed in self.keys: # self.keys-taulukosta saadaan selville # mitä toimintoa painettu nappi vastaa keynum = self.keys.index(keypressed) if keynum == 0: # Liikuta palkkia vasemmalle self.block_x -= 1 elif keynum == 1: # Liikuta palkkia oikealle self.block_x += 1 elif keynum == 2: # Pyöritä palkkia myötäpäivään self.block_rotation = (self.block_rotation + 1) % 4 elif keynum == 3: # Pyöritä palkkia vastapäivään self.block_rotation = (self.block_rotation - 1) % 4 else: # Tiputa palkki maahan. Eli luuppaa kunnes # osutaan seinään (self.doesHit()) while not self.doesHit(): self.block_y += 1 # 'Pudota' palkki peliareenalle self.dropBlock() # Jos siirto aiheutti seinään törmäämisen niin # palautetaan vanha sijainti ja rotaatio. if self.doesHit(): self.block_x = prev_x self.block_rotation = prev_rotation # block_movewait estää ettei palkit tipu ihan # koko ajan. if self.block_movewait > 0: self.block_movewait -= 1 else: self.block_movewait = 5 # Tiputa palkkia alaspäin self.block_y += 1 # Jos palkki törmää seinään, se pudotetaan peliareenalle if self.doesHit(): self.dropBlock() # Tämä funktio huolehtii palkin pudottamisesta # peliareenalle. Täällä myös katsotaan tuhoutuuko rivejä, # ja lisäillään pelaajan pisteitä sen mukaan def dropBlock(self): # Aluksi nostetaan palkkia vähän ylöspäin ettei se olisi # seinän sisässä. self.block_y -= 1 # Tämä pieni apufunktio piirtää palkin peliareenalle. def setWall(x, y): self.area[y][x] = self.block.color # self.iterateCurrentBlock käy nykyisen liikuteltavan # palikan läpi ja kutsuu jokaisessa kohdassa funktiota # joka sille on annettu parametrinä self.iterateCurrentBlock(setWall) # Seuraava koodinpätkä huolehtii rivien tuhoamisesta. # Hieman hankala ja epäoleellinen selittää, koeta # ymmärtää koodista ;). copyTarget = copySource = self.height - 1 linesErased = 0 while copyTarget >= 0: while copySource >= 0 and \ self.area[copySource].count(Board.emptyBlock) == 0: copySource -= 1 linesErased += 1 if copySource >= 0: self.area[copyTarget] = self.area[copySource] else: self.area[copyTarget] = [Board.emptyBlock for i in range(self.width)] copySource -= 1 copyTarget -= 1 # Lisää pisteitä sen mukaan kuinka monta riviä tuhoutui self.points += Board.pointIncreases[linesErased] # Rankaise vihollisia jos rivejä tuhoutui if linesErased > 0: for enemy in self.enemies: enemy.punish(linesErased) # Luodaan vielä lopuksi uusi palikka self.createBlock() # Jos uusi palikka osuu heti seinään, on peli ohi if self.doesHit(): self.gameover = True # Tarkistaa osuuko tällä hetkellä liikuteltava palikka seinään def doesHit(self): # hits on apumuuttuja joka kertoo osutaanko seinään vai ei. # Lue pep-227 jotta ymmärrät miksi hits on taulukko eikä boolean # http://www.python.org/peps/pep-0227.html # (etsi sivulta "bank_account") hits = [False] # Tämä apufunktio testaa osutaanko seinään tai # pelialueen ulkopuolelle def testHit(x, y): if x < 0 or x >= self.width or \ y >= self.height or self.area[y][x] != Board.emptyBlock: hits[0] = True # Taas käydään nykyinen palikka läpi, tällä kertaa funktion # testHit-avustuksella self.iterateCurrentBlock(testHit) return hits[0] # Käy tällä hetkellä liikuteltavan palikan läpi ja kutsuu jokaiselle # palikan palkille funktiota f. # f ottaa kaksi argumenttia, x:n ja y:n, jotka vastaavat sijaintia # pelikentällä. def iterateCurrentBlock(self, f): # Taas apufunktio. Tämä delegoi block.iterate:n antaman # x:n ja y:n funktiolle f. Mutta ensin lisää ko. x:ään ja y:hyn # palikan sijainnin peliareenalla. def func(x, y): if y + self.block_y >= 0: f(x + self.block_x, y + self.block_y) # self.iterateBlock on vastaava funktio mutta toimii # mille tahansa palikalle. self.block.iterate(self.block_rotation, func) # Piirrä teksti 'Next:' sekä sen alle seuraavaksi tuleva # palikka def drawNextBlock(self): # Piirrä 'Next:'-teksti self.console.text(self.x + self.width + 3, self.y + 3, "Next:") # Pyyhi edellinen palikka pois, käyttäen vihreitä pisteitä self.console.rectangle((self.x + self.width + 3, self.y + 4, self.x + self.width + 9, self.y + 10), 2, '.') # Laske väri seuraavaksi tulevalle palikalle color = self.nextBlock.color * 16 # Tämä funktio piirtää palkkia ruudulle def func(x, y): self.console.text(x + self.width + self.x + 5, y + self.y + 6, ' ', color) # Ja nyt käydään seuraava palikka läpi (kulma 0), piirtäen # jokainen sen palkki ruudulle func:n avulla. self.nextBlock.iterate(0, func) # Rankaistaan itseämme :). Tätä kutsuvat vihollispelaajat kun # saavat rivejä tuhotuksi. amount on 1 - 4, rankaisun määrä def punish(self, amount): for i in range(amount): # Näin saadaan yksi rivi tuhottua yhdellä rivillä self.area = self.area[1:] # Lisätään vielä pohjalle rivi jolla on satunnaista roskaa # värillä 7. garbage = [random.randint(0, 1) * 7 for i in range(self.width)] # Varmistetaan ettei tule riviä joka on täysi palkki if garbage.count(Board.emptyBlock) == 0: garbage[random.randrange(0, self.width)] = Board.emptyBlock self.area.append(garbage) if self.doesHit(): self.dropBlock() # Tämä funktio piirtää peliareenan, pisteet sekä liikuteltavan # palkin ruudulle def draw(self): # Ensin piirretään peliareena for xx in range(self.width + 2): for yy in range(self.height + 1): # Tämä piirtää reunat if xx == 0 or xx == self.width + 1 or yy == self.height: self.console.text(xx + self.x, yy + self.y, '#', 0x87) # Tämä piirtää tyhjää elif self.area[yy][xx - 1] == Board.emptyBlock: self.console.text(xx + self.x, yy + self.y, '.', 1) # Tämä piirtää palkin jos peli on loppunut (harmaa '#') elif self.gameover: self.console.text(xx + self.x, yy + self.y, '#') # Tämä piirtää normaalin värikkään palkin else: self.console.text(xx + self.x, yy + self.y, ' ', self.area[yy][xx - 1] * 16) # Jos peli ei ole päättynyt, piirretään liikuteltava palkki if not self.gameover: # Palkin väri color = self.block.color * 16 # Ja tässä näppärästi yhdellä lauseella palkin piirtäminen. # lambda luo nimettömän funktion joka on passeli tähän # paikkaan. self.iterateCurrentBlock(lambda xx, yy: self.console.text(xx + self.x + 1, yy + self.y, ' ', color)) # Piirrä pisteet self.console.text(self.x + self.width + 3, self.y, "Points:") self.console.text(self.x + self.width + 2, self.y + 1, str(self.points).rjust(7)) ################################################################# # # Täältä itse peli alkaa # ################################################################# def tetris(): # Luodaan eka konsoli console = Console.getconsole() console.title("Tetris") # Tällä funktiolla voidaan lukea näppikseltä merkki. Palauttaa # kyseisen merkin, esim 'w' tai 'Escape' tai 'Left' def readKeyboard(): while console.peek() is not None: # event on tapahtuma (esim. hiiren liikahdus ruudulla) event = console.get() # Mutta me välitetään vain napin painalluksista ('KeyPress') if event.type == 'KeyPress': if event.keysym: return event.keysym else: return event.char # Udellaan pelaajien määrä print 'How many players? (1,2 or 3)' key = None while key != '1' and key != '2' and key != '3': key = readKeyboard() players = int(key) # Pyyhitään ruutu tyhjäksi, eli piirrettään kuvaruudun kokoinen # suorakaide välilyöntiä (rectangle piirtää vakiona välilyöntiä) console.rectangle((0, 0, console.size()[0], console.size()[1])) # Luodaan jokaiselle pelaajalle lauta, taulukkoon 'boards'. boards = [Board(console, 5, 1, 10, 20, ['Left', 'Right', 'Up', 'Control_L', 'Down'])] if players >= 2: boards.append(Board(console, 30, 1, 10, 20, ['a', 'd', 'w', 'q', 's'])) # Lisää vihollinen (lauta nro 0) boards[1].addEnemy(boards[0]) boards[0].addEnemy(boards[1]) if players >= 3: boards.append(Board(console, 55, 1, 10, 20, ['j', 'l', 'i', 'u', 'k'])) # Lisää viholliset boards[2].addEnemy(boards[0]) boards[2].addEnemy(boards[1]) boards[0].addEnemy(boards[2]) boards[1].addEnemy(boards[2]) while 1: # Pikkasen paussia ettei pyöri liian nopeasti time.sleep(0.05) key = readKeyboard() # Lopeta luuppi jos painettiin esc:iä if key == 'Escape': break # Käy läpi kaikki laudat, liikuta palikoita niissä # ja piirrä ne. for i in range(len(boards)): boards[i].move(key) boards[i].draw() # Testaa onko yksinpeli vai moninpeli if players == 1: # Yksinpeli loppuu kun pelaaja kuolee (gameover) if boards[0].gameover: break else: # Moninpeli loppuu kun yksi pelaaja on enää elossa # (varmuuden vuoksi myös kun 0 pelaajaa on elossa, # jos kaksi sattuu kuolemaan juuri yhtäaikaa) # Tämän voisi muuten ilmaista ytimekkäämmin näin: # alivePlayers = len(filter(lambda x: not x.gameover, boards)) # Mutta ehkä uuden listan muodostaminen olisi vähän # hardcorea tätä tarkoitusta varten alivePlayers = 0 for board in boards: if not board.gameover: alivePlayers += 1 if alivePlayers <= 1: break # Peli loppui, tulosta jotain ruudulle console.text(9, 14, "+" + "-" * 24 + "+") console.text(9, 15, "|" + "Game over!".center(24) + '|') console.text(9, 16, "|" + "Press 'esc' to quit".center(24) + '|') console.text(9, 17, "+" + "-" * 24 + "+") # Ja odota kunnes käyttäjä painaa esc while readKeyboard() != 'Escape': pass # Peli käynnistyy vain jos Python on käynnistetty tästä # moduulista. if __name__ == '__main__': tetris() pikkumyy 12:40 19.4.03 Übersvalt! theril 17:59 21.4.03 "The Console module is currently only available for Windows 95, 98, NT, and 2000." empty 16:22 22.4.03 Sori Theril :P. Toinen vaihtoehto tekstigrafiikkaan olisi ollut Pythonin mukana tuleva curses-moduuli, mutta se taas toimii tällä hetkellä vain Unix:ssa. Seuraavan koodinpätkäni kyllä kirjoitan käyttämään pygame-moduulia, joka löytyy Win/Mac/Unix:lle. http://www.pygame.org/ empty 11:09 26.4.03 Muutin Board.doesHit()-funktiota hieman. Nyt se on oikeaoppisempi joskaan ei juurikaan selkeämpi (aiemmin se käytti self.hits-muuttujaa, mutta apumuuttujan tunkeminen olioon oli vähän arvelluttavaa) empty 08:42 11.5.03 Kansa tahtoo lisää Pythonia! ane 21:51 6.8.03 NIIN! Python-alue tänne! empty 13:51 13.3.04 Loistavaa. pyGame viela kayttoon niin tulee mukavempana. Python sektiota tarvittaisiin. |
![]() Haku
|