DeTrapper

Iš wiki.angis.net.
Jump to navigation Jump to search

DeTrapper žaidimas

Taip atrodo DeTrapper žaidimo vaizdas

Ši programa demonstruoja, kaip susikurti klasikinį minisweper stiliaus žaidimą, kuriame spaudinėjant ant langelių reikia suprasti, kuriuose langeliuose yra minos. Kiekviename neužminuotame langelyje būna arba skaičius, kuris nurodo šalia esančių minų kiekį arba tuščias (saugus plotas).

Šioje programėlėje panaudoti keli skirtingi vaizdo failai ir garsai, kurie atsiranda priklausomai nuo atlikto veiksmo.

Žaisti DeTrapper

Žaidimui reikalingi failai

Visus garsus ir vaizdus panaudotus šiame žaidime galima atsisiųsti čia.

DeTrapper žaidimo kodas

reikalinga angis
reikalingas žaidimas
import random


class TextBoard:
    def __init__(self, x, y, size, color):
        self.__color = color
        self.__text = žaidimas.Tekstas('')
        self.__text.atsirask (x, y)
        self.__text.spalva(self.__color)
        self.__text.dydis(size)
        self.__text.hide()

    def show(self):
        self.__text.show()

    def hide(self):
        self.__text.hide()

    def set_color(self, color):
        self.__color = color
        self.__text.spalva(color)

    def update_text(self, message):
        self.__text.tekstas(message)


class TimerBoard:
    def __init__(self, x, y, sound_player = None):
        spalva = '#AA0000'
        dydis = 20
        self.TIME_LIMIT = 999
        self.__x = x
        self.__y = y
        self.seconds = 0
        self.time_text = žaidimas.Tekstas('■■■')
        self.time_text.spalva(spalva)
        self.time_text.dydis(dydis)
        self.text_stopped = žaidimas.Tekstas('000')
        self.text_stopped.spalva(spalva)
        self.text_stopped.dydis(dydis)
        self.time_label = žaidimas.Tekstas('Time:')
        self.time_label.spalva(spalva)
        self.time_label.dydis(dydis - 4)
        # self.tekstas_stopped.hide()
        self.is_running = False
        self.timer_clock = None
        self.__sfx = sound_player
    
    def __get_text(self):
        return str(self.seconds).zfill(3)

    def __update(self):
        if self.is_running:
            self.seconds += 1
            if self.seconds >= self.TIME_LIMIT:
                self.seconds = self.TIME_LIMIT
                self.stop_timer()
            self.time_text.tekstas(self.__get_text())
            self.text_stopped.hide()
            if self.__sfx is not None:
                self.__sfx.play_sound('timer-tick')
            self.time_text.show(self.__x + 42, self.__y)
        else:
            self.time_text.hide()
            self.text_stopped.show(self.__x + 42, self.__y)

    def reset(self):
        self.seconds = 0
        self.stop_timer()
        self.display()

    def display(self):
        self.time_label.show(self.__x, self.__y + 2)
        if self.is_running:
            self.text_stopped.hide()
            self.time_text.tekstas(self.__get_text())
            self.time_text.show(self.__x + 42, self.__y)
        else:
            self.time_text.hide()
            self.text_stopped.tekstas(self.__get_text())
            self.text_stopped.show(self.__x + 42, self.__y)

    def start_timer(self):
        if not self.is_running:
            if self.timer_clock == None:
                self.timer_clock = angis.Taimeris(self.__update, 1)
            self.is_running = True
            self.display()

    def stop_timer(self):
        if self.is_running:
            self.is_running = False
            # self.timer_clock.stok()
            self.text_stopped.tekstas(self.__get_text())


class CoordsConveter:
    def __init__(self):
        self.areas = [
            ('tile', 51, 51, 500, 500),
            ('menu_m', 50, 10, 150, 30),
            ('menu_r', 250, 10, 310, 30)
        ]
    
    def calc_click_result(self, x, y):
        tile_col = x // 50
        tile_row = y // 50
        for area in self.areas:
            if area[1] <= x and x <= area[3] and area[2] <= y and y <= area[4]:
                return (area[0], tile_col, tile_row)
        return ('none', 0, 0)
            

class SoundPlayer:
    def __init__(self):
        self.__sfx = { }
        self.__is_muted = False

    def preload_sounds(self):
        sfx_list = ['trap-activated', 'timer-tick', 'slash', 'flag-in', 'flag-out', 'multi-clear', 'congrats', 'try-again']
        sfx_files = [f'garsai/{sfx}.mp3' for sfx in sfx_list]
        angis.naudosiuFailus(sfx_files)
        for sfx in sfx_list:
            self.__sfx[sfx] = angis.Garsas(f"garsai/{sfx}.mp3")

    def play_sound(self, sfx):
        if (not self.__is_muted) and (sfx in self.__sfx):
            self.__sfx[sfx].grokFone()

    def toggle_sound(self):
        self.__is_muted = not self.__is_muted
        return self.__is_muted


class TrapField:
    def __init__(self, sound_player):
        self.COLOR_MENU_TITLE = '#663300'
        self.COLOR_MENU_TEXT = '#884400'

        self.__border_parts = [ [1, 2, 3], [4, 14, 6], [7, 8, 9] ]
        self.__closed_tile = 'pav/traps/closed-field.png'
        self.__flagged_tile = 'pav/traps/flagged-field.png'
        self.__hint_files = [f'pav/traps/open-field-{hint}.png' for hint in range(10)]
        self.__board_tiles = []
        self.__sfx = sound_player
        self.__help_texts = []
        self.__is_help_visible = False

    def preload_pictures(self):
        gfx_files = [f'pav/plyt/{self.__border_parts[i][j]}.png' for i in range(3) for j in range(3)]
        gfx_files.append(self.__closed_tile)
        gfx_files.append(self.__flagged_tile)
        for hint in self.__hint_files:
            gfx_files.append(hint)
        angis.naudosiuFailus(gfx_files)
    
    def preload_sounds(self):
        self.__sfx.preload_sounds()

    def draw_board(self, width, height, scene):
        # border with empty field
        for y in range(height + 2):
            row = 0
            if 0 == y:
                row = 0
            elif y < height + 1:
                row = 1
            else:
                row = 2
            
            row_tiles = []
            for x in range(width + 2):
                col = 0
                if 0 == x:
                    col = 0
                elif x < width + 1:
                    col = 1
                else:
                    col = 2
                
                tile = scene.sukurkPlytelę(f'pav/plyt/{self.__border_parts[row][col]}.png', x, y)
                row_tiles.append(tile)
            self.__board_tiles.append(row_tiles)
        # covered mine field 
        for y in range(1, height + 1):
            for x in range(1, width + 1):
                self.__board_tiles[y][x].naudokPaveiksliuką(self.__closed_tile)
    
    def get_closed_pic(self):
        return self.__closed_tile
  
    def update_tile(self, x, y, value):
        if 0 <= value <= 8:
            self.__board_tiles[y][x].background(self.__hint_files[value])
        elif value == 9:
            # self.__sfx.play_sound('trap-activated')
            self.__board_tiles[y][x].background(self.__hint_files[9])
        elif value == -1:
            # this place is not suitable for playing sound, cause on restart it happens <=81 times
            # self.__sfx.play_sound('flag-out')
            self.__board_tiles[y][x].background(self.__closed_tile)
        elif value == 10:
            # self.__sfx.play_sound('flag-in')
            self.__board_tiles[y][x].background(self.__flagged_tile)

    def toggle_help(self, width, height):
        if not self.__is_help_visible:
            # empty field
            for y in range(1, height + 1):
                for x in range(1, width + 1):
                    self.__board_tiles[y][x].background(f'pav/plyt/{self.__border_parts[1][1]}.png')
            if len(self.__help_texts) == 0:
                self.__help_texts = [
                    TextBoard(100, 80, 24, self.COLOR_MENU_TITLE),
                    TextBoard(100, 120, 18, self.COLOR_MENU_TEXT),
                    TextBoard(100, 150, 18, self.COLOR_MENU_TEXT),
                    TextBoard(100, 180, 18, self.COLOR_MENU_TEXT),
                    TextBoard(100, 210, 18, self.COLOR_MENU_TEXT),
                    TextBoard(100, 240, 18, self.COLOR_MENU_TEXT),
                    TextBoard(100, 260, 18, self.COLOR_MENU_TEXT)
                ]
                self.__help_texts[0].update_text('===== Help =====')
                self.__help_texts[1].update_text('[H] - toggle help: on/off')
                self.__help_texts[2].update_text('[M] - toggle mode: dig /flag')
                self.__help_texts[3].update_text('[R] - restart game')
                self.__help_texts[4].update_text('[S] - toggle sound: on/off')
                self.__help_texts[5].update_text('You can also use mouse to change mode')
                self.__help_texts[6].update_text('or restart the game')
            for txt in self.__help_texts:
                txt.show()
        else:
            for txt in self.__help_texts:
                txt.hide()
        self.__is_help_visible = not self.__is_help_visible
        return self.__is_help_visible


class DeTrapperGame:
    def __init__(self, width=9, height=9, traps_count=10):
        
        self.COLOR_DEFAULT = '#333333'
        self.COLOR_FOCUS_RESTART = '#3333AA'

        self.__width = width
        self.__height = height
        self.__traps_count = traps_count
        self.__sfx = SoundPlayer()
        self.__gfx = TrapField(self.__sfx)
        self.__scene = žaidimas.duokSceną()
        self.__converter = CoordsConveter()
        # field click mode: dig / flag 
        self.__mode_display = TextBoard(50, 10, 16, self.COLOR_DEFAULT)
        self.__restart_display = TextBoard(250, 10, 16, self.COLOR_DEFAULT)
        self.__restart_display.update_text('[R]estart')
        self.__time_display = TimerBoard(420, 10, self.__sfx)
        self.__info_display = TextBoard(50, 515, 16, self.COLOR_DEFAULT)
        self.MODES = [ 'dig', 'flag' ]
        self.__init_game()
        self.__draw_init_board()
        self.__started = False
        self.__is_help_visible = False

    def __init_game(self):
        self.__mode = 0
        self.__update_mode_display()
        self.__info_display.set_color(self.COLOR_DEFAULT)
        self.__info_display.update_text('[H] - toggle help on/off')
        self.__info_display.show()
        self.__is_help_visible = False
        self.ended = False
        self.won = False
        self.__board = []
        self.view = []
        for y in range(self.__height + 2):
            self.__board.append([])
            self.view.append([])
            for x in range(self.__width + 2):
                self.__board[y].append(0)
                self.view[y].append(0)
        self.__randomize_traps()
        self.__add_hints()

    def __randomize_traps(self):
        n = 0
        while n < self.__traps_count:
            x = random.randint(1, self.__width)
            y = random.randint(1, self.__height)
            if self.__board[y][x] == 0:
                self.__board[y][x] = 9
                n = n + 1

    def __add_hints(self):
        for y in range(self.__height + 2):
            for x in range(self.__width + 2):
                if self.__board[y][x] == 9:
                    for dy in range(-1, 2):
                        for dx in range(-1, 2):
                            if dy == 0 and dx == 0:
                                continue
                            
                            if self.__board[y+dy][x+dx] != 9:
                                self.__board[y+dy][x+dx] += 1

    def __update_mode_display(self):
        if self.__mode == 0:
            self.__mode_display.set_color(self.COLOR_DEFAULT)
        else:
            self.__mode_display.set_color(self.COLOR_FOCUS_RESTART)
        mode_name = self.MODES[self.__mode]
        self.__mode_display.update_text(f'[M]ode: {mode_name}')
        
    def toggle_mode(self):
        self.__mode = (self.__mode + 1) % 2
        self.__update_mode_display()
        # debug 
        # self.show_message(f'New mode: {self.__mode}')

    def toggle_flag(self, x, y):
        if self.view[y][x] == 0:
            self.view[y][x] = 2
            self.__sfx.play_sound('flag-in')
            return 10
            # self.__gfx.update_tile(x, y, 10)
        elif self.view[y][x] == 2:
            self.view[y][x] = 0
            self.__sfx.play_sound('flag-out')
            return -1
            # self.__gfx.update_tile(x, y, -1)
        
        return self.__board[y][x]
    
    def toggle_sound(self):
        self.__sfx.toggle_sound()

    def show_message(self, text):
        self.__info_display.update_text(text)

    def open_field(self, x, y):
        if self.ended:
            return
        
        # when it's a flagged field or already opened - do nothing
        if x < 1 or self.__width < x or y < 1 or self.__height < y:
            # self.show_message(f'Out of bounds: {x}, {y} ')
            return

        if (self.__mode == 0 and self.view[y][x]==2) or self.view[y][x] == 1:
            # self.show_message(f"Can't click: ({x},{y}) - m={self.__mode}, v={self.view[y][x]}")
            return

        # self.show_message(f'Click: {x}, {y}, m={self.__mode}, v={self.view[y][x]}')
        if self.__mode == 1:
            value = self.toggle_flag(x, y)
            self.update_board(x, y, False)
            return

        if self.__board[y][x] == 0:
            # recursion to open all zero fields
            # does not work with bigger boards due to deepness limit!
            self.__sfx.play_sound('multi-clear')
            self.__clear_empty_zone(x, y)
        else:
            if self.__board[y][x] == 9:
                self.ended = True
                self.won = False
                value = 9
                self.__sfx.play_sound('trap-activated')
            else:
                self.__sfx.play_sound('slash')
            self.view[y][x] = 1

        # create method to check game status (won/lost)
        self.__check_completeness()
        self.update_board(x, y, True)

    def __clear_empty_zone(self, x, y):
        if x < 1 or self.__width + 1 <= x or y < 1 or self.__height + 1 <= y:
            return

        if self.view[y][x] != 0:
            return

        self.view[y][x] = 1
        if self.__board[y][x] > 0:
            return

        if self.view[y-1][x-1] == 0:
            self.__clear_empty_zone(x-1, y-1)
        
        if self.view[y-1][x] == 0: 
            self.__clear_empty_zone(x, y-1)
        
        if self.view[y-1][x+1] == 0:
            self.__clear_empty_zone(x+1, y-1)
        
        if self.view[y][x-1] == 0: 
            self.__clear_empty_zone(x-1, y)
        
        if self.view[y][x+1] == 0: 
            self.__clear_empty_zone(x+1, y)

        if self.view[y+1][x-1] == 0:
            self.__clear_empty_zone(x-1, y+1)
        
        if self.view[y+1][x] == 0:  
            self.__clear_empty_zone(x, y+1)
        
        if self.view[y+1][x+1] == 0:
            self.__clear_empty_zone(x+1, y+1)

    def is_solved(self):
        closed_count = 0
        for y in range(1, self.__height + 1):
            for x in range(1, self.__width + 1):
                if self.view[y][x] != 1 or self.__board[y][x] == 9:
                    closed_count += 1
        return closed_count == self.__traps_count

    def __draw_init_board(self):
        self.__gfx.preload_pictures()
        self.__gfx.preload_sounds()
        self.__gfx.draw_board(self.__width, self.__height, self.__scene)
        self.__mode_display.show()
        self.__restart_display.show()
        self.__time_display.display()

    def update_board(self, x, y, full=False):
        if full:
            value = 0
            for y in range(1, self.__height + 1):
                for x in range(1, self.__width + 1):
                    view = self.view[y][x]
                    value = self.__board[y][x]
                    self.__update_field(x, y, view, value)
        else:
            cell_value = -1
            if self.view[y][x] == 2:
                cell_value = 10
            elif self.view[y][x] == 1:
                cell_value = self.__board[y][x]
            self.__gfx.update_tile(x, y, cell_value)
    
    def __update_field(self, x, y, view, value):
        cell_value = -1
        if view == 2:
            cell_value = 10
        elif view == 1:
            cell_value = value
        self.__gfx.update_tile(x, y, cell_value)

    def __check_completeness(self):
        if not self.ended:
            self.won = self.is_solved()
            self.ended = self.won
        
        if self.ended:
            self.__time_display.stop_timer()
            self.__time_display.display()
            self.__restart_display.set_color(self.COLOR_FOCUS_RESTART)
            if self.won:
                self.__sfx.play_sound('congrats')
                self.__info_display.set_color('#008800')
                self.show_message(f'GG - You won! Press [R] to play again.')
            else:
                self.__sfx.play_sound('try-again')
                self.__info_display.set_color('#FF0000')
                self.show_message(f'Game is over! You lost. Press [R] to restart.')

    def restart(self):
        if self.__is_help_visible:
            self.toggle_help()

        self.__init_game()
        # self.show_message('')
        self.__sfx.play_sound('multi-clear')
        self.update_board(0, 0, True)
        self.__time_display.reset()
        self.__started = False
        self.__restart_display.set_color(self.COLOR_DEFAULT)        

    def toggle_help(self):
        self.__is_help_visible = self.__gfx.toggle_help(self.__width, self.__height)
        if not self.__is_help_visible:
            self.update_board(-1, -1, True)

    def __handle_mouse_click(self, pointerX, pointerY):
        element, col, row = self.__converter.calc_click_result(pointerX, pointerY)
        # debug
        # self.show_message(f'{element} ({col}, {row})')
        if element == 'tile':
            if self.__is_help_visible:
                return
            if not self.__started:
                self.__started = True
                self.__time_display.start_timer()
            self.open_field(col, row)
        elif element == 'menu_m':
            self.toggle_mode()
        elif element == 'menu_r':
            self.restart()

    def bind_mouse(self):
        žaidimas.pelęPaspaudus(self.__handle_mouse_click)



# pradžia
FIELD_WIDTH = 9
FIELD_HEIGHT = 9
TOTAL_TRAPS_COUNT = 10


splayer = SoundPlayer()
splayer.preload_sounds()
def test_sound_player_tick():
    splayer.play_sound('dig')

def test_sound_player_trap():
    splayer.play_sound('multi-clear')


def play_game():
    # initiates game, draws board 
    game = DeTrapperGame(FIELD_WIDTH, FIELD_HEIGHT, TOTAL_TRAPS_COUNT)
    
    # žaidimas.pelęAtleidusDešinįKlavišą(toggle_flag)
    game.bind_mouse()

    # klaviatūra: mode, restart
    angis.atleidus('m', game.toggle_mode)
    angis.atleidus('M', game.toggle_mode)
    angis.atleidus('r', game.restart)
    angis.atleidus('R', game.restart)
    angis.atleidus('s', game.toggle_sound)
    angis.atleidus('S', game.toggle_sound)
    angis.atleidus('h', game.toggle_help)
    angis.atleidus('H', game.toggle_help)


play_game()

Svarbu! Nepamiršk įsikelti žaidime naudojamus paveiksliukus ir garsus per failų tvarkyklę. Visi DeTrapper žaidimui reikalingi failai yra čia.