TENUKIS v0.6
簡易ですが、メニュー画面的を実装しました。機能するのは "new_game" と "exit" だけなんですが。
ゲームクリアの概念を導入しました。 999 ミノ耐えたらクリアです。
エンドレスモードもそのうち実装します。
あとコードの見通しを良くするため、 Mino 及びそのサブクラスは別ファイルに分割。
# -*- coding:utf-8 -*- import pygame from pygame.locals import * import random from datetime import timedelta from mino import * SCR_W = 320 # 表示ウィンドウの横幅 SCR_H = 240 # 表示ウィンドウの縦幅 KEY_ROTATE_R = K_KP2 KEY_ROTATE_L1 = K_KP1 KEY_ROTATE_L2 = K_KP3 BG_COLOR = pygame.Color(128,128,128) #MINO_SET = [MinoT, MinoI, MinoO, MinoZ, MinoS, MinoL, MinoJ] class RenderableNumber(): def __init__(self, screen, format, position, font_size=24, value=0): self.font = pygame.font.Font(None, font_size) self.screen = screen self._value = value self.format = format self.position = position def get(self): return self._value def set(self, v): self._value = v self.render() def __iadd__(self, v): self.set(self._value + v) return self def render(self): v = self.font.render(self.format % self.get(), True, (255,255,255), BG_COLOR) self.screen.fill( BG_COLOR, (self.position, (v.get_width()+self.font.get_height(), self.font.get_height())) ) self.screen.blit( v, self.position) class Score(RenderableNumber): def __init__(self, screen): self.font = pygame.font.Font(None, 32) # フォントを読み込む self.screen = screen self._value = 0 self._text = self.font.render("score", True, (255,255,255)) self.screen.blit( self._text, (260,0)) self.render() def render(self): v = self.font.render( str(self.get()), True, (255,255,255), (128,128,128)) self.screen.fill( BG_COLOR, ((self.screen.get_width()-v.get_width()-30, 32), (v.get_width()+30, 32)) ) self.screen.blit( v, (self.screen.get_width() - v.get_width()-5, 32)) class Game(): """ 1ゲーム """ FPS = 60 #frame per second MSPF = 1000/FPS #milliseconds per frame MAX_ASOBI = 60 #設置後固定までのフレーム数 MAX_DELETE_TIME = 8 #列が消える場合の次ミノ落下までのフレーム数 MAX_WAIT_BEFORE_MINO = 25 #設置(&削除)後、次のミノの登場までのフレーム数 WIDTH = 10 HEIGHT = 20 CLEAR_LENGTH = 999 #このミノが固定したらクリア。 def __init__(self, screen, block_size=10): self.screen = screen self.block_size = block_size self.surface = pygame.Surface(( block_size*Game.WIDTH, block_size*Game.HEIGHT )) self.next_surface = pygame.Surface(( block_size*4, block_size*4 )) #次ミノ表示 self.clock = pygame.time.Clock() self.asobi = Game.MAX_ASOBI self.max_asobi = Game.MAX_ASOBI self.delete_time = Game.MAX_DELETE_TIME self.wait_before_mino = Game.MAX_WAIT_BEFORE_MINO self.field = [[ None for i in range(Game.WIDTH)] for j in range(Game.HEIGHT+2)] #10*20のマス。Noneが空、[0-6]が色。 self.minogen = MinoGenerator( self.field ) self.mino = self.minogen.next() self.next_mino = self.minogen.next() self.score = Score(self.screen) self.mino_length = RenderableNumber(self.screen, "mino : %d /"+str(Game.CLEAR_LENGTH), (200,160)) self.deleted_length = RenderableNumber(self.screen, "line : %d", (200, 190)) self.controller = ControllerManager() self.start_time = False self.sounds = { "appear0": pygame.mixer.Sound("appear0.wav"), "appear1": pygame.mixer.Sound("appear1.wav"), "rotate_r": pygame.mixer.Sound("rotate_r.wav"), "rotate_l": pygame.mixer.Sound("rotate_l.wav"), "delete0": pygame.mixer.Sound("delete0.wav"), "delete1": pygame.mixer.Sound("delete1.wav"), "delete2": pygame.mixer.Sound("delete2.wav"), "delete3": pygame.mixer.Sound("delete3.wav"), "delete4": pygame.mixer.Sound("delete4.wav"), "fall": pygame.mixer.Sound("fall.wav"), "ready": pygame.mixer.Sound("ready.wav") } self.render() self.render_next() self.sounds["ready"].play() self.wait(60) self.start_time = pygame.time.get_ticks() #これとの差分から経過時間を。 def main_loop(self): while(True): if pygame.event.get(QUIT) or self.controller.to_quit(): return if self.mino: self.tick() #FPS #遊びを減らす self.asobi -= 1 if self.asobi < 0: self.settle_and_next() self.render() next else: #回転→移動、順らしい。 #回転処理等 if self.controller.to_rotate_r(): self.rotate_r() elif self.controller.to_rotate_l(): self.rotate_l() if self.controller.to_settle(): self.settle_and_next() #移動処理 if self.controller.to_move_l(): self.move_l() elif self.controller.to_move_r(): self.move_r() if self.fall(): self.sounds["fall"].stop() self.sounds["fall"].play() self.render() next def settle_and_next(self): """ self.mino を self.field に固定し、新ミノを出す """ for block in self.mino.get_block_positions(): self.field[block[1]][block[0]] = self.mino.__class__.color_id self.mino = None self.render() if self.delete_line(): #delete_line()は消える列があれば消してTrueを返す self.render() self.wait( self.delete_time ) if self.mino_length.get() >= Game.CLEAR_LENGTH: #ゲームクリア処理 raise GameOver(self.score.get(),self.deleted_length.get(),self.mino_length.get(),timedelta(milliseconds=pygame.time.get_ticks()-self.start_time),clear=True ) self.wait( (self.wait_before_mino) ) self.mino = self.next_mino self.asobi = self.max_asobi #回転チェック pygame.event.pump() #おまじない kp = pygame.key.get_pressed() if kp[KEY_ROTATE_R]: if self.mino.rotate_r(): self.sounds["rotate_r"].play() else: self.sounds["appear0"].play() elif kp[KEY_ROTATE_L1] or kp[KEY_ROTATE_L2]: if self.mino.rotate_l(): self.sounds["rotate_l"].play() else: self.sounds["appear1"].play() else: if random.randint(0,1): self.sounds["appear0"].play() else: self.sounds["appear1"].play() self.next_mino = self.minogen.next() self.render_next() pygame.event.clear() self.mino_length += 1 if not self.mino.can_alive(self.mino.get_blocks(), self.mino.position, self.field): #将来的にはコード変えたい。 self.render() pygame.display.flip() raise GameOver( self.score.get(), self.deleted_length.get(), self.mino_length.get(), timedelta(milliseconds=pygame.time.get_ticks()-self.start_time) ) self.fall() return def delete_line(self): """ 列を消す。消した列数を返す。 """ deleted = 0 for line in xrange(len(self.field)): is_filled = True for block in self.field[line]: if block == None: is_filled = False if is_filled: del self.field[line] self.field.insert(0, [None for i in xrange(self.WIDTH)]) deleted += 1 if deleted: self.score += [0,100,300,700,1200][deleted] #点数 pd = self.deleted_length.get() self.deleted_length += deleted if pd/10 < self.deleted_length.get()/10: #桁が増えたとき self.max_asobi -= 1 if self.deleted_length.get()/10 % 5 == 0: #50行ごとに待ち時間を減らす self.wait_before_mino -= 1 self.sounds["delete"+str(deleted)].play() return deleted def move_l(self): self.mino.move_l() def move_r(self): self.mino.move_r() def rotate_l(self): self.mino.rotate_l() def rotate_r(self): self.mino.rotate_r() def fall(self): if self.mino.fall(): self.asobi = self.max_asobi return True else: return False def tick(self): """ 1 フレーム """ self.clock.tick( Game.FPS ) #FPS #入力状態の処理 self.controller.update() #時計の描画 position = (200,100) str_delta = str( timedelta(milliseconds=pygame.time.get_ticks()-self.start_time))[2:-3] if self.start_time else "0:00:00" v = (pygame.font.Font(None, 18)).render( str_delta, True, (255,255,255), BG_COLOR) self.screen.fill( BG_COLOR, (position, (v.get_width()+10, 18)) ) self.screen.blit( v, position) pygame.display.flip() def wait(self, frame): for i in xrange(frame): self.tick() def _render_block(self, position, color): """ position = (x,y) のマスを描画する """ pygame.draw.rect(self.surface, color, pygame.Rect( (self.block_size*position[0], self.block_size*(position[1])), (self.block_size, self.block_size) )) def render(self): """ メイン画面を描画する。ただし実際の描画はGame.tickで。 """ self.surface.fill( (0,0,0) ) # 固定済みブロックの描画 for ln in xrange(len(self.field)-2): line = ln+2 for masu in xrange(len(self.field[line])): if( self.field[line][masu] != None ): self._render_block( (masu, line-2), MINO_SETTLED_COLORS[self.field[line][masu]] )#(220,220,220) ) # 操作中のミノの表示 if self.mino: for block in self.mino.get_block_positions(): self._render_block( (block[0], block[1]-2), MINO_COLORS[self.mino.color_id])#(255,255,255) ) # screen に書き付ける。 self.screen.blit(self.surface, (10,20)) #pygame.display.flip() def render_next(self): """ 次ミノを描画する """ self.next_surface.fill((0,0,0)) for block in self.next_mino.blocks[0]: pygame.draw.rect(self.next_surface, MINO_COLORS[self.next_mino.color_id], pygame.Rect( (self.block_size*block[0], self.block_size*block[1]), (self.block_size, self.block_size) )) self.screen.blit(self.next_surface, (120,20)) pygame.display.flip() class GameOver(Exception): def __init__(self, score,line,mino,time, clear=False): self.score = score self.line = line self.mino = mino self.time = time self.clear = clear def __str__(self): return "GAME OVER -- socre:" + str(self.score) class ControllerManager: ASOBI = 6 #移動の遊び def __init__(self): self._state = { "quit" : False, "rotate_r" : False, "rotate_l" : False, "move_r" : False, "move_l" : False, "settle" : False } self.asobi = ControllerManager.ASOBI def update(self): for item in self._state: self._state[item] = False for event in pygame.event.get(KEYDOWN): if event.key == K_ESCAPE: self._state["quit"] = True elif event.key == KEY_ROTATE_R: self._state["rotate_r"] = True elif (event.key == KEY_ROTATE_L1 or event.key == KEY_ROTATE_L2): self._state["rotate_l"] = True elif event.key == K_DOWN: self._state["settle"] = True pygame.event.clear(KEYDOWN) pygame.event.pump() #おまじない kp = pygame.key.get_pressed() if kp[K_LEFT]: if self.asobi == ControllerManager.ASOBI: #コンコン self._state["move_l"] = True if self.asobi > 0: self.asobi -= 1 else: self._state["move_l"] = True elif kp[K_RIGHT]: if self.asobi == ControllerManager.ASOBI: #コンコン self._state["move_r"] = True if self.asobi > 0: self.asobi -= 1 else: self._state["move_r"] = True else: self.asobi = ControllerManager.ASOBI def to_quit(self): return self._state["quit"] def to_rotate_r(self): return self._state["rotate_r"] def to_rotate_l(self): return self._state["rotate_l"] def to_move_r(self): return self._state["move_r"] def to_move_l(self): return self._state["move_l"] def to_settle(self): return self._state["settle"] class Menu: def __init__(self, items): self.items = items self.selected = 0 self.render() def render(self): surface = pygame.Surface( (200,200) ) surface.fill( (0,0,0) ) font = pygame.font.Font(None, 20) for i in xrange(len(self.items)): color = (255,255,255) if i==self.selected else (128,128,128) surface.blit( font.render( self.items[i], True, color ), (0, 25*i) ) global screen screen.blit( surface, (20,20) ) pygame.display.flip() return def wait_choose(self): print "push any key to select" while True: for event in pygame.event.get(KEYDOWN): if event.key == K_DOWN: self.selected +=1 if self.selected >= len(self.items): self.selected = 0 self.render() elif event.key == K_UP: self.selected -=1 if self.selected < 0: self.selected = len(self.items)-1 self.render() else: return self.items[self.selected] def new_game(): global screen screen.fill( BG_COLOR ) pygame.display.flip() game = Game(screen) game.main_loop() def main(): pygame.mixer.pre_init(channels=1, buffer=1024) pygame.init() # pygameの初期化 global screen screen = pygame.display.set_mode( (SCR_W, SCR_H) ) # 画面を作る pygame.display.set_caption('TENUKIS') # タイトル screen.fill( BG_COLOR ) pygame.display.flip() menu = Menu( ("new_game", "endless_game", "score", "setting", "exit") ) while True: menu.render() pygame.time.wait(500) pygame.event.clear() select = menu.wait_choose() if select == "new_game": try: new_game() except GameOver as go: pygame.time.wait(1000) print "Score:%d by %d lines from %d minos in %s" % (go.score, go.line, go.mino, str(go.time)[:-3]) next elif select == "endless_game": raise NotImplementedError elif select == "score": raise NotImplementedError elif select == "setting": raise NotImplementedError elif select == "exit": return if __name__ == '__main__': main() # end of file
ただ、ゲームなんか作ってる場合か!という気もしないでもないので、少し開発停滞するかもです。