Prg2/design patterns 1

จาก Theory Wiki
รุ่นแก้ไขเมื่อ 02:40, 16 มีนาคม 2564 โดย Jittat (คุย | มีส่วนร่วม) (→‎Codes)
(ต่าง) ←รุ่นแก้ไขก่อนหน้า | รุ่นแก้ไขล่าสุด (ต่าง) | รุ่นแก้ไขถัดไป→ (ต่าง)
ไปยังการนำทาง ไปยังการค้นหา
This is part of Programming 2 2563

Basic information

Clips

Codes

Observer Pattern (OO version)

File: gamelib.py
class GameApp(ttk.Frame): 
    def __init__(self, parent, canvas_width=800, canvas_height=500, update_delay=33):
        # ...
        self.on_key_pressed_observers = []
    
    # ...
 
    def register_on_key_pressed_observer(self, observer):
        self.on_key_pressed_observers.append(observer)

    def on_key_pressed(self, event):
        for observer in self.on_key_pressed_observers:
            observer.notify(event)
File: monkeys.py
class MonkeyGame(GameApp):
    class AppObserver:
        def __init__(self, app):
            self.app = app

    class SpeedAdjustmentObserver(AppObserver):
        def notify(self, event):
            app = self.app
            if event.char == '+':
                if app.speed < 10:
                    app.speed += 1
                    app.update_speed_text()
                    
            if event.char == '-':
                if app.speed > 1:
                    app.speed -= 1
                    app.update_speed_text()

    class BananaThrowingObserver(AppObserver):
        def notify(self, event):
            app = self.app
            if event.char == ' ':
                if not app.banana.is_moving:
                    app.banana.set_speed(3 * app.speed, 5 * app.speed)
                    app.banana.reset()
                    app.banana.start()

    # ...
        
    def init_game(self):
        # ...

        self.register_on_key_pressed_observer(MonkeyGame.SpeedAdjustmentObserver(self))
        self.register_on_key_pressed_observer(MonkeyGame.BananaThrowingObserver(self))

Observer Pattern (functions)

File: gamelib.py
class GameApp(ttk.Frame): 
    def __init__(self, parent, canvas_width=800, canvas_height=500, update_delay=33):
        # ...

        self.on_key_pressed_handlers = []

    # ...

    def register_on_key_pressed_handler(self, f):
        self.on_key_pressed_handlers.append(f)

    def on_key_pressed(self, event):
        for f in self.on_key_pressed_handlers:
            f(event)
File: monkeys.py
class MonkeyGame(GameApp):
    def handle_speed_adjustment_key_pressed(self, event):
        if event.char == '+':
            if self.speed < 10:
                self.speed += 1
                self.update_speed_text()
                
        if event.char == '-':
            if self.speed > 1:
                self.speed -= 1
                self.update_speed_text()

    def handle_banana_throwing_key_pressed(self, event):
        if event.char == ' ':
            if not self.banana.is_moving:
                self.banana.set_speed(3 * self.speed, 5 * self.speed)
                self.banana.reset()
                self.banana.start()
    # ...

    def init_game(self):
        # ...

        self.register_on_key_pressed_handler(self.handle_speed_adjustment_key_pressed)
        self.register_on_key_pressed_handler(self.handle_banana_throwing_key_pressed)

Command Pattern

# ...

class DotUpdateCommand:
    def __init__(self, dot):
        self.dot = dot

    def excute(self):
        self.old_state = self.dot.get_state()
        self.dot.real_update()        

    def undo(self):
        self.dot.set_state(self.old_state)

class Dot(Sprite):
    def init_element(self):
        self.vx = 0
        self.vy = 0

    def random_speed(self):
        self.vx = 5 * randint(-5,5)
        self.vy = -5 * randint(1,10)

    def bounce(self):
        if (self.x > CANVAS_WIDTH) or (self.x < 0):
            self.vx = -self.vx

        if self.y > CANVAS_HEIGHT:
            self.vy = -0.85 * self.vy

    def real_update(self):
        self.x += self.vx
        self.y += self.vy
        self.vy += GRAVITY

        self.bounce()

    def get_update_command(self):
        return DotUpdateCommand(self)

    def get_state(self):
        return (self.x, self.y, self.vx, self.vy)

    def set_state(self, state):
        self.x, self.y, self.vx, self.vy = state

class FlappyGame(GameApp):
    def create_sprites(self):
        self.dots = []
        for i in range(NUM_BALLS):
            dot = Dot(self, 'images/dot.png', CANVAS_WIDTH // 2, CANVAS_HEIGHT // 2)
            dot.random_speed()

            self.dots.append(dot)
            self.elements.append(dot)

    def init_game(self):
        self.create_sprites()

        self.commands = []
        self.is_reversed = False
        self.cmd_index = 0

    def reverse_update(self):
        current_commands = self.commands[self.cmd_index]

        for c in reversed(current_commands):
            c.undo()

        self.cmd_index -= 1
        if self.cmd_index < 0:
            self.is_reversed = False
            self.commands = []

    def create_update_commands(self):
        new_commands = []
        for dot in self.dots:
            new_commands.append(dot.get_update_command())
        return new_commands

    def pre_update(self):
        if self.is_reversed:
            self.reverse_update()
            return

        new_commands = self.create_update_commands()
        for c in new_commands:
            c.execute()

        self.commands.append(new_commands)
        self.cmd_index = len(self.commands) - 1

    def on_key_pressed(self, event):
        self.is_reversed = True

# ...

Boucing ball code: github

State Pattern

import math

# ...

SPEED = 10

DIR_UP = 1
DIR_RIGHT = 2
DIR_DOWN = 3
DIR_LEFT = 4

DIR_OFFSETS = {
    DIR_UP: (0,-1),
    DIR_RIGHT: (1,0),
    DIR_DOWN: (0,1),
    DIR_LEFT: (-1,0),
}

class DotNormalState:
    def __init__(self, dot):
        self.dot = dot

    def update(self):
        dot = self.dot
        dot.x += SPEED * DIR_OFFSETS[dot.direction][0]
        dot.y += SPEED * DIR_OFFSETS[dot.direction][1]
        dot.wrap_position()
        
class DotExcitedState:
    def __init__(self, dot):
        self.dot = dot
        self.counter = 0

    def update(self):
        dot = self.dot
        dot.x += 3 * SPEED * DIR_OFFSETS[dot.direction][0]
        dot.y += 3 * SPEED * DIR_OFFSETS[dot.direction][1]
        dot.wrap_position()

        self.counter += 1

        if self.counter == 100:
            dot.state = DotNormalState(dot)

class RedDot(Sprite):
    instance = None

    @staticmethod
    def get_instance(app):
        if not RedDot.instance:        
            RedDot.instance = RedDot(app, 'images/reddot.png')
        return RedDot.instance

    def random_position(self):
        self.x = randint(20, CANVAS_WIDTH - 20)
        self.y = randint(20, CANVAS_HEIGHT - 20)


class Dot(Sprite):
    def init_element(self):
        self.direction = DIR_UP
        self.state = DotNormalState(self)

    def wrap_position(self):
        if self.x < 0:
            self.x = CANVAS_WIDTH
        if self.x > CANVAS_WIDTH:
            self.x = 0
        if self.y < 0:
            self.y = CANVAS_HEIGHT
        if self.y > CANVAS_HEIGHT:
            self.y = 0

    def get_excited(self):
        self.state = DotExcitedState(self)

    def update(self):
        self.state.update()

    def is_close_to(self, dot):
        dist = math.sqrt((self.x - dot.x) ** 2 + (self.y - dot.y) ** 2)
        return dist < 10


class FlappyGame(GameApp):
    def create_sprites(self):
        self.dot = Dot(self, 'images/dot.png', CANVAS_WIDTH // 2, CANVAS_HEIGHT // 2)

        self.red_dot = RedDot.get_instance(self)
        self.red_dot.random_position()

        self.elements.append(self.dot)
        self.elements.append(self.red_dot)

    def init_game(self):
        self.create_sprites()

    def post_update(self):
        if self.dot.is_close_to(self.red_dot):
            self.dot.get_excited()

            self.red_dot.random_position()

    def on_key_pressed(self, event):
        if self.dot.direction == DIR_UP:
            self.dot.direction = DIR_RIGHT
        else:
            self.dot.direction = DIR_UP

# ...