diff --git a/Progress Report - George.md b/Progress Report - George.md new file mode 100644 index 0000000..9e1916e --- /dev/null +++ b/Progress Report - George.md @@ -0,0 +1,39 @@ +## Progress Report - George + +#### Main.py + +The `main.py` is a file I created which is meant to bootstrap the application. This file initiates *PyGame*, imports `App`, and starts the game. Run this file to run the game. + +Originally the `pygame` was initialized in the `App` class, see the **App** header for more. + +#### The App Class + +PyGame itself provides very little framework. All you really need to do is call `pygame.init()` and start drawing to the window. For this reason I created the `App` class which provides a better framework and separation of logic. + +The class follows the singleton pattern (i.e. there is one instance of the class that can be accessed statically) and the instance is accessed using `App.instance`. + +The class encapsulates all the objects and logic required to run the game. When `App.start()` is called, PyGame is instructed to display a window and `App` enters the game-loop by running `App.loop()`. The game loop runs while `App._running` is set to `True` and it has several discrete steps: + +1. Handle PyGame events + 1. The `App.handle_events()` method loops through the current PyGame events and dispatches appropriately. + 1. If the event is `pygame.QUIT` then the `_running` flag is set to false and the game loop exits. + 2. If the event is a keyboard event, it is passed to `App.keyboard(event)`, and if it's a `MOUSEBUTTONDOWN` event, `App.click(event)` +2. Run game logic + 1. The `App.logic()` method is called, which is where all the game logic is meant to be encapsulated. If this were a live-action game then this is where the code to move physics objects would be applied. +3. Render the game: After all the events have been handled and the game logic has been run, the game needs to be drawn to the screen! + 1. The `App.render()` method is invoked, in this method: + 1. The screen is cleared using `App.fill_color` + 2. Stuff is rendered to the screen, which "stuff" will depend on the `App.render_option` flag. This is because the game has multiple states like the title screen, maybe a pause menu, and the game itself. + 3. We call `pygame.display.flip()` which applies all the rendering operations we have done to the screen + +Once the loop has exited, `App.cleanup()` is called. This is where all necessary cleaning up operations like quitting PyGame, closing file handles, and maybe writing log data can be handled. + +#### The Button Class + +This class is my naive implementation of the classic button UI element. It's initialized with an xy position, width, height, style object, text string, and an on click (callable) handler. + +##### How to use it + +1. Instantiate an instance of `Button`, give it an appropriate position, width, height, etc and give it a proper `on_click` handler. The `on_click` handler can be a function or a lambda. +2. Handle clicking events. It's a button! We need to have it respond to when it's clicked. The way to do this is to call `Button.handle_click(MOUSEVENT)`. This method takes a mouse event and determines if the click happened within its own bounds and triggers the `on_click` handler if necessary. I recommend you place this in `App.click()` and pass the mouse event. +3. Ensure that the button gets rendered. Do this by inserting a `Button.render()` call inside `App.render()`. Please ensure that the button is only being rendered if you are checking mouse events and vice versa, you can do both of these optionally. \ No newline at end of file diff --git a/src/gui/app.py b/src/gui/app.py index e57ba14..37fc470 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -17,7 +17,7 @@ class App: button_font = pygame.font.SysFont("Calibri", 30) - class render_option(Enum): + class mode(Enum): title_screen = 0 game = 1 settings = 2 @@ -30,16 +30,23 @@ def __init__(self): self.size = self.width, self.height = (1000, 1000) - self.render_opt = App.render_option.title_screen + self.mode = App.mode.title_screen - self.button = Button(100, 100, 100, 100, Style(bg = (255,255,255), hbg = (0,0,0), tcolor = (255,0,0)), - "abc", lambda: print(crayons.cyan('clicked!'))) + self.button = Button(100, 100, 100, 100, + "abc", lambda: print(crayons.cyan('clicked!')), Style(bg = (255,255,255), hbg = (0,0,0), tcolor = (255,0,0))) - self.exit = Button(0, 0, 200, 70, Style(bg = (233, 30, 99), hbg = (255, 96, 144), tcolor = (0,0,0)), - "Exit", lambda: App.instance.stop()) + self.exit = Button(0, 0, 200, 70, + "Exit", lambda: App.instance.stop(), Style(bg = (233, 30, 99), hbg = (255, 96, 144), tcolor = (0,0,0))) + + self.play_button = Button(300, 450, 300, 200, "Play", lambda: App.instance.set_mode(App.mode.game)) + + print(crayons.green('Instantiated App')) + def set_mode(self, mode): + self.mode = mode + def stop(self): self._running = False @@ -65,10 +72,12 @@ def click(self, event): pos = pygame.mouse.get_pos() - if self.render_opt == App.render_option.game: - self.button.check_mouse(pos) + if self.mode == App.mode.game: + self.button.handle_click(pos) + elif self.mode == App.mode.title_screen: + self.play_button.handle_click(pos) - self.exit.check_mouse(pos) + self.exit.handle_click(pos) def handle_events(self): """Loops through pygame events and handle them""" @@ -98,19 +107,20 @@ def render(self): self.screen.fill(self.fill_color) # Clear screen # Begin drawing code - if self.render_opt == App.render_option.title_screen: - # Render Title Screen + if self.mode == App.mode.title_screen: + # Render Title Screenpasspasspass - self.screen.blit(self.background, (0, 0)) - self.screen.blit(self.title, (235, 150)) - self.screen.blit(self.play_button, (300, 450)) - self.screen.blit(self.info_button, (300, 600)) - elif self.render_opt == App.render_option.game: + self.screen.blit(App.background, (0, 0)) + self.screen.blit(App.title, (235, 150)) + self.screen.blit(App.play_button, (300, 450)) + self.screen.blit(App.info_button, (300, 600)) + self.play_button.render() + elif self.mode == App.mode.game: # Render game self.button.render() - elif self.render_opt == App.render_option.settings: + elif self.mode == App.mode.settings: pass self.exit.render() diff --git a/src/gui/button.py b/src/gui/button.py index a2ff101..a7d4a60 100644 --- a/src/gui/button.py +++ b/src/gui/button.py @@ -5,22 +5,26 @@ class Button: - def __init__(self, x, y, width, height, style, text, on_click): + def __init__(self, x, y, width, height, text, on_click, style = Style((255,255,255), (255,255,255), (0,0,0))): self.x = x self.y = y self.width = width self.height = height self.style = style - self.text = gui.app.App.button_font.render(text, True, (0, 0, 0)) if text else None + self.text = gui.app.App.button_font.render(text, True, style.tcolor) if text else None self.text_size = gui.app.App.button_font.size(text) - self.text_pos = ((self.x+self.width-self.text_size[0])//2, (self.y+self.height-self.text_size[1])//2) + self.text_pos = ((self.x)+(self.width-self.text_size[0])//2, (self.y)+(self.height-self.text_size[1])//2) self.on_click = on_click + self.disabled = False def check_bounds(self, x, y): return self.x < x < self.x + self.width and self.y < y < self.y + self.height def handle_click(self, mouse): + if self.disabled: + return + x = mouse[0] y = mouse[1] @@ -28,10 +32,13 @@ def handle_click(self, mouse): self.on_click() def render(self): + + if self.disabled: + return x, y = pygame.mouse.get_pos() - pygame.draw.rect(gui.app.App.instance.screen, self.style.hbg if self.check_bounds(x,y) else self.style.bg, (self.x, self.y, self.width, self.height)) + pygame.draw.rect(gui.app.App.instance.screen, self.style.hbg if self.style.hbg and self.check_bounds(x,y) else self.style.bg, (self.x, self.y, self.width, self.height)) if self.text: gui.app.App.instance.screen.blit(self.text, self.text_pos) \ No newline at end of file diff --git a/src/launch.sh b/src/launch.sh new file mode 100644 index 0000000..5b426e8 --- /dev/null +++ b/src/launch.sh @@ -0,0 +1 @@ +/usr/bin/env python3 main.py \ No newline at end of file