Aston’s Fusion Homepage

NARA & DVIDS Public Domain Archive
  • Second Project – CAD

    Overview

    I made a mechanism that can open and close a claw. It is controlled by sliding one part.

    Skills

    Sketching

    I drew a hooked stick and two straight sticks for the grabber, slider, and connection parts.

    I drew a rectangle split into three sections for the shaft.

    Each part has appropriate holes of diameter 5mm.

    Sketch for the Grabber Part.
    Extruding

    The entire mechanism will be split into three vertical 2.5mm thick layers: Shaft Floor, Lower, and Upper.

    The grabber will consist of two parts: a handle, a hook. The handle will be 1 layer and the hook is 2 layers thick.

    The slider is 2 layers thick, and the connector is 1 layer thick.

    The shaft is made of the shaft floor, walls, and joint. The walls and joint are on the lower layer.

    Extrusion of Handle and Hook for the Grabber Part.
    Assembly

    I connected the grabber to connectors and connectors to the slider with revolve mates.

    The slider and shaft with a slider mate.

    The shaft and the origin with a fasten mate.

    Full Assembly
    Mechanical Drawing

    For each part:

    1. Top/Bottom
    2. Left/Right
    3. Isometric
    4. Appropriate Measurements

    For the assembly:

    1. Isometric
    Mechanical Drawing for the Grabber Part.
    Bill of Materials

    https://sgs.onshape.com/documents/da92b8df7297a672c9fd55ea/w/34b0cf1f63d108998f1efccf/e/94408fd0c280806c1f893b32?renderMode=0&uiState=68e2b66bf844e7cf94edf4c9

    AI Usage

    I did not use AI for this assignment.

  • First Project – Calendar App

    Intent

    For this project I made a Calendar App where you can add events and view your event schedule for the day. These events can also be repeating.

    This project is meant as a tool to help with time-management and planning.

    Code Planning

    Part One: Save Event Format

    1. Events are listed in increasing end time order.
    2. Event Format
      • -Completeness
      • -EndTime
      • -StartTime
      • -StepTime
      • -Description
      • #

    Example Save File

    -0
    -1759302000
    -1756710000
    -86400
    -Daily 7am reminder 
    #

    Part Two: Load Event Format

    1. Each event will be stored as a list
      • [end time, start time, step amt, name, desc, finished]
    2. Multiple events will be stored in a master list and sorted by increasing end time.

    Part Three: Get Events on a Specific Day

    Returns a list of event indices and event times

    1. First Check: Did this group of events already end
    2. Second Check: Did this group of events start yet
    3. Third Check: Is there an event TODAY.
      • Let k be the smallest integer where
        • Start + k * Step > Date
      • The event is TODAY if
        • Start + k * step < Date + 86400
    4. Calculate the time of the event on that day
      • Event Time = Start + k * step – Date
    5. Add tuple to a list
      • (Event Index, Event Time)
    6. Return this list

    Part Four: Get and Update Inputs of Mouse and Keyboard

    1. Save Inputs from Previous Frame
    2. Save Inputs from Current Frame
    3. Save Mouse Position

    Part Five: Query Inputs of Mouse and Keyboard

    1. Parameters: Mode (String), Key (id)
      • Key identifies which specific input we are querying.
    2. “down” Mode: Return if the specific input is on
    3. “press” Mode: Return if the specific input was turned on this frame
    4. “release” Mode: Return if the specific input was turned off this frame

    Part Six: Add Event

    1. Get Inputs: Start Time, End Time, Step, Name, and Description
    2. Insert Event into Events list while maintaining sorted by end time. (USE LOAD EVENT FORMAT)

    Part Seven: Remove Event

    1. Delete event from events list.

    Note: All times will be saved using seconds since epoch.

    Flowchart

    The following flowchart shows the structure and logic of how the above is implemented.

    Flowchart

    Code

    File Loader Class
    To save and load events and get events on a specific day. (See parts One to Three)
    class CalendarLoader:
        def __init__(self, filename):
            self.events = []
            self.filename = filename
    
        def set_file(self, filename):
            self.filename = filename
    
        def load_events(self, filepath=None):
            if filepath is None:
                filepath = self.filename
            self.events = []
            infile = open(filepath, "r")
            lines = [] # all the lines between previous and next "#" markings
            for line in infile:
                if line[0] == '#':
                    # gets event settings based on above format 
                    b = int(lines[0])
                    end = int(lines[1])
                    start = int(lines[2])
                    step = int(lines[3])
                    name = lines[4]
                    desc = "\n".join(lines[5:])
                    self.events.append([end, start, step, name, desc, b])
                    lines = [] # reset lines 
                else:
                    # add line to lines list. 
                    lines.append(line[1:-1])
            infile.close()
    
        def save_events(self, filepath=None):
            if filepath is None:
                filepath = self.filename
            s = ""
            for event in self.events:
                # Saves each event based on the above format 
                s += f"-{int(event[5])}\n"
                s += f"-{int(event[0])}\n"
                s += f"-{int(event[1])}\n"
                s += f"-{int(event[2])}\n"
                s += f"-{event[3]}\n"
                s += f"-{event[4]}\n"
                s += "#\n"
            outfile = open(filepath, "w")
            outfile.write(s)
            outfile.close()
    
        def search(self, time):
            # Basic Binary Search
            low = -1
            high = len(self.events) - 1
            while low < high:
                mid = low + (high - low + 1) // 2
                if self.events[mid][0] <= time:
                    low = mid
                else:
                    high = mid - 1
            return low + 1
    
        def getDay(self, date):
            # Follows the above Instructions to get all the events that happen on that day. 
            events = []
            for i in range(len(self.events) - 1, -1, -1):
                event = self.events[i]
                if event[0] < date:
                    break
                k = (date - event[1] - 1) // event[2] + 1
                t = event[1] + k * event[2] - date
                if k >= 0 and t < 86400:
                    events.append((t, i))
            return events
    
        def getEvent(self, idx):
            return self.events[idx]
    Pygame Input Handler
    To handle updating and querying inputs from the mouse and keyboard. (See parts Four to Five)
    import pygame
    
    class InputHandler:
        # Get Modes: "down", "press", and "release" 
        def __init__(self):
            self.keyDown = pygame.key.get_pressed()
            self.prevKeyDown = pygame.key.get_pressed()
            self.mouseDown = pygame.mouse.get_pressed(3)
            self.prevMouseDown = pygame.mouse.get_pressed(3)
            self.mousePos = pygame.mouse.get_pos()
    
        def update(self):
            # Set Previous Mouse and Key Downs 
            self.prevKeyDown = self.keyDown
            self.prevMouseDown = self.mouseDown
            # Set Current Mouse and Key Downs 
            self.keyDown = pygame.key.get_pressed()
            self.mouseDown = pygame.mouse.get_pressed(3)
            self.mousePos = pygame.mouse.get_pos()
    
        def getKey(self, key, mode="down"):
            match mode:
                case "down": # Is Key Down 
                    return self.keyDown[key]
                case "press": # Was Key Pressed this frame 
                    return self.keyDown[key] and not self.prevKeyDown[key]
                case "release": Was Key Released this frame 
                    return self.prevKeyDown[key] and not self.keyDown[key]
                case _:
                    print(f"Unknown Mode {mode}")
                    return False
    
        def getMouse(self, mouse, mode="down"):
            match mode:
                case "down": # Is Mouse Button Down 
                    return self.mouseDown[mouse]
                case "press": # Was Mouse Button Pressed this frame 
                    return self.mouseDown[mouse] and not self.prevMouseDown[mouse]
                case "release": # Was Mouse Button Release this frame 
                    return self.prevMouseDown[mouse] and not self.mouseDown[mouse]
                case _:
                    print(f"Unknown Mode {mode}")
                    return False
    
    Primary Calendar Class
    To Add and Remove events. (See parts Six to Seven)
    import datetime
    import time
    import pygame
    from CalendarLoader import *
    
    utc = datetime.timezone.utc
    
    class Calendar:
        def __init__(self, filename):
            self.loader = CalendarLoader(filename)
            self.loader.load_events()
            # Floor "now" to 00:00 on the same day 
            now = (time.time() // 86400) * 86400
            self.line = 20
            self.date = now
            self.start = now
            self.end = now
            self.step = 86400
            self.addIdx = 0
            self.selected = 0
    
        def set_filename(self, filename):
            self.loader.set_file(filename)
    
        def render(self, font, inHand, offset):
            # Get all the events on the current date 
            events = sorted(self.loader.getDay(self.date))
            relx, rely = inHand.mousePos[0] - offset[0], inHand.mousePos[1] - offset[1]
            idx = -1
            if 20 < relx < 250 and 5 + 1.5 * self.line < rely:
                idx = int((rely - 5) / self.line - 1.5)
            if idx >= len(events):
                idx = -1
            if inHand.getMouse(0, "press") and idx != -1:
                self.selected = idx # Check if any events where clicked 
            if inHand.getKey(pygame.K_UP, "press"):
                self.addIdx = (self.addIdx - 1 + 3) % 3 # decrease add index 
            if inHand.getKey(pygame.K_DOWN, "press"):
                self.addIdx = (self.addIdx + 1) % 3 # increase add index 
    
            # Calendar Window 
            surf = pygame.Surface((600, 400))
            surf.fill((0, 255, 255))
            surf.blit(font.render(datetime.datetime.fromtimestamp(self.date, utc).strftime("%A %B %d, %Y"), True, (0, 0, 0)), (20, 5))
            for i, (t, eventIdx) in enumerate(events):
                event = self.loader.getEvent(eventIdx)
                timing = datetime.datetime.fromtimestamp(t, utc).strftime("%I:%M %p")
                colour = ((0, 0, 0) if event[5] != 1 else (0, 180, 0)) if i != self.selected else (0, 0, 127)
                status = "[]" if event[5] == 1 else "X"
                surf.blit(font.render(f"{status} {timing}: {event[3]}", True, colour), (20, 5 + (i + 1.5) * self.line))
                if i == self.selected:
                    surf.blit(font.render(datetime.datetime.fromtimestamp(event[1], utc).strftime("Start: %A %B %d, %Y - %H"), True, (0, 0, 0)), (270, 5 + 9 * self.line))
                    surf.blit(font.render(datetime.datetime.fromtimestamp(event[0], utc).strftime("End: %A %B %d, %Y - %H"), True, (0, 0, 0)), (270, 5 + 10 * self.line))
                    surf.blit(font.render(datetime.datetime.fromtimestamp(event[2] - 86400, utc).strftime("Step: %d days; %H hours"), True, (0, 0, 0)), (270, 5 + 11 * self.line))
                    surf.blit(font.render("=== Description ===", True, (0, 0, 0)), (270, 5 + 12 * self.line))
                    surf.blit(font.render(f"{event[4]}", True, (0, 0, 0)), (270, 5 + 13 * self.line))
            if inHand.getKey(pygame.K_f, "press") and 0 <= self.selected < len(events):
                self.loader.events[events[self.selected][1]][5] = 1 - self.loader.events[events[self.selected][1]][5]
            colours = [(0, 0, 0), (0, 0, 0), (0, 0, 0)]
            colours[self.addIdx] = (0, 0, 127)
            surf.blit(font.render(datetime.datetime.fromtimestamp(self.start, utc).strftime("Start: %A %B %d, %Y - %H"), True, colours[0]), (270, 5))
            surf.blit(font.render(datetime.datetime.fromtimestamp(self.end, utc).strftime("End: %A %B %d, %Y - %H"), True, colours[1]), (270, 5 + self.line))
            surf.blit(font.render(datetime.datetime.fromtimestamp(self.step - 86400, utc).strftime("Step: %d days; %H hours"), True, colours[2]), (270, 5 + 2 * self.line))
            surf.blit(font.render(("START", "END", "STEP")[self.addIdx], True, (0, 127, 0)), (300, 5 + 4 * self.line))
            clickIdx = (-1, -1)
            for t in range(2):
                for i in range((4, 4, 2)[self.addIdx]):
                    bx = 370 + i * 25
                    by = 5 + 4 * self.line + t * 25
                    pygame.draw.rect(surf, (0, 0, 0), (bx - 10, by - 10, 20, 20), 2, 4)
                    symbol = "+" if t == 0 else "-"
                    surf.blit(font.render(symbol, True, (0, 0, 0)), (bx - 3, by - 13))
                    if abs(relx - bx) < 10 and abs(rely - by) < 10:
                        clickIdx = (i, t)
            if inHand.getMouse(0, "press") and clickIdx[0] != -1:
                m = (1, -1)[clickIdx[1]]
                i = clickIdx[0]
                match self.addIdx:
                    case 0:
                        if i == 1:
                            self.start += m * 86400
                        else:
                            dt = datetime.datetime.fromtimestamp(self.start, utc)
                            para = [dt.month, dt.day, dt.year, dt.hour]
                            if i == 0:
                                para[i] = (para[i] + m + 11) % 12 + 1
                            elif i == 3:
                                para[i] = (para[i] + m + 24) % 24
                            else:
                                para[i] += m
                            self.start = datetime.datetime(para[2], para[0], para[1], para[3], tzinfo=utc).timestamp()
                    case 1:
                        if i == 1:
                            self.end += m * 86400
                        else:
                            dt = datetime.datetime.fromtimestamp(self.end, utc)
                            para = [dt.month, dt.day, dt.year, dt.hour]
                            if i == 0:
                                para[i] = (para[i] + m + 11) % 12 + 1
                            elif i == 3:
                                para[i] = (para[i] + m + 24) % 24
                            else:
                                para[i] += m
                            self.end = datetime.datetime(para[2], para[0], para[1], para[3], tzinfo=utc).timestamp()
                    case 2:
                        self.step += m * (86400, 3600)[i]
                self.start = max(self.start, 0)
                self.step = max(self.step, 86400)
                self.end = max(self.end, 0)
    
            clickIdx = -1
            surf.blit(font.render("Events: Add    Remove    Save", True, (0, 0, 0)), (270, 7 * self.line - 8))
            for t in range(3):
                bx = 340 + t * 55
                by = 5 + 8 * self.line
                pygame.draw.rect(surf, (0, 0, 0), (bx - 10, by - 10, 20, 20), 2, 4)
                symbol = "+-S"[t]
                o = 2 if t == 2 else 0
                surf.blit(font.render(symbol, True, (0, 0, 0)), (bx - 3 - o, by - 13 + o))
                if abs(relx - bx) < 10 and abs(rely - by) < 10:
                    clickIdx = t
            if inHand.getMouse(0, "press") and clickIdx != -1:
                if clickIdx == 0:
                    print("Name Of Event: ")
                    name = input()
                    desc = ""
                    print("Description: ")
                    while True:
                        add = input()
                        if add == "":
                            break
                        desc += add + '\n'
                    index = self.loader.search(time.time() - 2073600)
                    self.loader.events = self.loader.events[index:]
                    self.loader.events.insert(self.loader.search(self.end), [self.end, self.start, self.step, name, desc[:-1], 0])
                elif clickIdx == 1:
                    if 0 <= self.selected < len(events):
                        self.loader.events.pop(events[self.selected][1])
                elif clickIdx == 2:
                    self.loader.save_events()
    
            return surf
    
    Pygame Main Loop
    To integrate all parts.
    import pygame
    from InputHandler import *
    from Calendar import *
    
    # Basic Pygame Setup
    pygame.init()
    pygame.font.init()
    high_score = 0
    score = 0
    fps = 60
    fpsClock = pygame.time.Clock()
    
    width, height = 840, 480
    screen = pygame.display.set_mode((width, height))
    pygame.display.set_caption("Calendar App")
    
    font = pygame.font.SysFont("comicsans", 25)
    inHand = InputHandler()
    calendar = Calendar("event.txt")
    
    # print(calendar.loader.events)
    
    # Main Game Loop
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                running = False
    
        if not running:
            break
        inHand.update()
    
        # White Background 
        screen.fill((255, 255, 255))
    
        # Handles Click Detection for the Calendar Date Controller 
        clickIdx = (-1, -1)
        screen.blit(font.render("Calendar Date Controller", True, (0, 0, 0)), (630, 18))
        for i in range(3):
            by = 55 + i * 25
            label = ("Year", "Month", "Day")[i]
            screen.blit(font.render(f"{label}: ", True, (0, 0, 0)), (630, by - 12))
            for t in range(2):
                bx = 700 + t * 25
                pygame.draw.rect(screen, (0, 0, 0), (bx - 10, by - 10, 20, 20), 2, 4)
                symbol = "+" if t == 0 else "-"
                screen.blit(font.render(symbol, True, (0, 0, 0)), (bx - 3, by - 13))
                if abs(inHand.mousePos[0] - bx) < 10 and abs(inHand.mousePos[1] - by) < 10:
                    clickIdx = (i, t)
        if inHand.getMouse(0, "press") and clickIdx[0] != -1:
            m = (1, -1)[clickIdx[1]]
            i = clickIdx[0]
            if i == 2:
                calendar.date += m * 86400
            else:
                dt = datetime.datetime.fromtimestamp(calendar.date, utc)
                para = [dt.year, dt.month, dt.day]
                if i == 1:
                    para[i] = (para[i] + m + 11) % 12 + 1
                else:
                    para[i] += m
                calendar.date = datetime.datetime(para[0], para[1], para[2], tzinfo=utc).timestamp()
            calendar.date = max(calendar.date, 0)
    
        screen.blit(calendar.render(font, inHand, (20, 20)), (20, 20))
    
        pygame.display.flip()
        fpsClock.tick(fps)
    
    # Closed the game
    print("App Closed Successfully")
    
    Single-File Full Code
    import time
    import pygame
    import datetime
    
    # ============ FILE LOADER ============ 
    
    class CalendarLoader:
        def __init__(self, filename):
            self.events = []
            self.filename = filename
    
        def set_file(self, filename):
            self.filename = filename
    
        def load_events(self, filepath=None):
            if filepath is None:
                filepath = self.filename
            self.events = []
            infile = open(filepath, "r")
            lines = []
            for line in infile:
                if line[0] == '#':
                    b = int(lines[0])
                    end = int(lines[1])
                    start = int(lines[2])
                    step = int(lines[3])
                    name = lines[4]
                    desc = "\n".join(lines[5:])
                    self.events.append([end, start, step, name, desc, b])
                    lines = []
                else:
                    lines.append(line[1:-1])
            infile.close()
    
        def save_events(self, filepath=None):
            if filepath is None:
                filepath = self.filename
            s = ""
            for event in self.events:
                s += f"-{int(event[5])}\n"
                s += f"-{int(event[0])}\n"
                s += f"-{int(event[1])}\n"
                s += f"-{int(event[2])}\n"
                s += f"-{event[3]}\n"
                s += f"-{event[4]}\n"
                s += "#\n"
            outfile = open(filepath, "w")
            outfile.write(s)
            outfile.close()
    
        def search(self, time):
            # Basic Binary Search
            low = -1
            high = len(self.events) - 1
            while low < high:
                mid = low + (high - low + 1) // 2
                if self.events[mid][0] <= time:
                    low = mid
                else:
                    high = mid - 1
            return low + 1
    
        def getDay(self, date):
            # Follows the above Instructions to get all the events that happen on that day. 
            events = []
            for i in range(len(self.events) - 1, -1, -1):
                event = self.events[i]
                if event[0] < date:
                    break
                k = (date - event[1] - 1) // event[2] + 1
                t = event[1] + k * event[2] - date
                if k >= 0 and t < 86400:
                    events.append((t, i))
            return events
    
        def getEvent(self, idx):
            return self.events[idx]
    
    # ============ PYGAME INPUT HANDLER ============ 
    
    class InputHandler:
        def __init__(self):
            self.keyDown = pygame.key.get_pressed()
            self.prevKeyDown = pygame.key.get_pressed()
            self.mouseDown = pygame.mouse.get_pressed(3)
            self.prevMouseDown = pygame.mouse.get_pressed(3)
            self.mousePos = pygame.mouse.get_pos()
    
        def update(self):
            self.prevKeyDown = self.keyDown
            self.prevMouseDown = self.mouseDown
            self.keyDown = pygame.key.get_pressed()
            self.mouseDown = pygame.mouse.get_pressed(3)
            self.mousePos = pygame.mouse.get_pos()
    
        def getKey(self, key, mode="down"):
            match mode:
                case "down":
                    return self.keyDown[key]
                case "press":
                    return self.keyDown[key] and not self.prevKeyDown[key]
                case "release":
                    return self.prevKeyDown[key] and not self.keyDown[key]
                case _:
                    print(f"Unknown Mode {mode}")
                    return False
    
        def getMouse(self, mouse, mode="down"):
            match mode:
                case "down":
                    return self.mouseDown[mouse]
                case "press":
                    return self.mouseDown[mouse] and not self.prevMouseDown[mouse]
                case "release":
                    return self.prevMouseDown[mouse] and not self.mouseDown[mouse]
                case _:
                    print(f"Unknown Mode {mode}")
                    return False
    
    # ============ CALENDAR CLASS ============ 
    
    utc = datetime.timezone.utc
    
    class Calendar:
        def __init__(self, filename):
            self.loader = CalendarLoader(filename)
            self.loader.load_events()
            now = (time.time() // 86400) * 86400
            self.line = 20
            self.date = now
            self.start = now
            self.end = now
            self.step = 86400
            self.addIdx = 0
            self.selected = 0
    
        def set_filename(self, filename):
            self.loader.set_file(filename)
    
        def render(self, font, inHand, offset):
            events = sorted(self.loader.getDay(self.date))
            relx, rely = inHand.mousePos[0] - offset[0], inHand.mousePos[1] - offset[1]
            idx = -1
            if 20 < relx < 250 and 5 + 1.5 * self.line < rely:
                idx = int((rely - 5) / self.line - 1.5)
            if idx >= len(events):
                idx = -1
            if inHand.getMouse(0, "press") and idx != -1:
                self.selected = idx
            if inHand.getKey(pygame.K_UP, "press"):
                self.addIdx = (self.addIdx - 1 + 3) % 3
            if inHand.getKey(pygame.K_DOWN, "press"):
                self.addIdx = (self.addIdx + 1) % 3
    
            surf = pygame.Surface((600, 400))
            surf.fill((0, 255, 255))
            surf.blit(font.render(datetime.datetime.fromtimestamp(self.date, utc).strftime("%A %B %d, %Y"), True, (0, 0, 0)), (20, 5))
            for i, (t, eventIdx) in enumerate(events):
                event = self.loader.getEvent(eventIdx)
                timing = datetime.datetime.fromtimestamp(t, utc).strftime("%I:%M %p")
                colour = ((0, 0, 0) if event[5] != 1 else (0, 180, 0)) if i != self.selected else (0, 0, 127)
                status = "[]" if event[5] == 1 else "X"
                surf.blit(font.render(f"{status} {timing}: {event[3]}", True, colour), (20, 5 + (i + 1.5) * self.line))
                if i == self.selected:
                    surf.blit(font.render(datetime.datetime.fromtimestamp(event[1], utc).strftime("Start: %A %B %d, %Y - %H"), True, (0, 0, 0)), (270, 5 + 9 * self.line))
                    surf.blit(font.render(datetime.datetime.fromtimestamp(event[0], utc).strftime("End: %A %B %d, %Y - %H"), True, (0, 0, 0)), (270, 5 + 10 * self.line))
                    surf.blit(font.render(datetime.datetime.fromtimestamp(event[2] - 86400, utc).strftime("Step: %d days; %H hours"), True, (0, 0, 0)), (270, 5 + 11 * self.line))
                    surf.blit(font.render("=== Description ===", True, (0, 0, 0)), (270, 5 + 12 * self.line))
                    surf.blit(font.render(f"{event[4]}", True, (0, 0, 0)), (270, 5 + 13 * self.line))
            if inHand.getKey(pygame.K_f, "press"):
                self.loader.events[events[self.selected][1]][5] = 1 - self.loader.events[events[self.selected][1]][5]
            colours = [(0, 0, 0), (0, 0, 0), (0, 0, 0)]
            colours[self.addIdx] = (0, 0, 127)
            surf.blit(font.render(datetime.datetime.fromtimestamp(self.start, utc).strftime("Start: %A %B %d, %Y - %H"), True, colours[0]), (270, 5))
            surf.blit(font.render(datetime.datetime.fromtimestamp(self.end, utc).strftime("End: %A %B %d, %Y - %H"), True, colours[1]), (270, 5 + self.line))
            surf.blit(font.render(datetime.datetime.fromtimestamp(self.step - 86400, utc).strftime("Step: %d days; %H hours"), True, colours[2]), (270, 5 + 2 * self.line))
            surf.blit(font.render(("START", "END", "STEP")[self.addIdx], True, (0, 127, 0)), (300, 5 + 4 * self.line))
            clickIdx = (-1, -1)
            for t in range(2):
                for i in range((4, 4, 2)[self.addIdx]):
                    bx = 370 + i * 25
                    by = 5 + 4 * self.line + t * 25
                    pygame.draw.rect(surf, (0, 0, 0), (bx - 10, by - 10, 20, 20), 2, 4)
                    symbol = "+" if t == 0 else "-"
                    surf.blit(font.render(symbol, True, (0, 0, 0)), (bx - 3, by - 13))
                    if abs(relx - bx) < 10 and abs(rely - by) < 10:
                        clickIdx = (i, t)
            if inHand.getMouse(0, "press") and clickIdx[0] != -1:
                m = (1, -1)[clickIdx[1]]
                i = clickIdx[0]
                match self.addIdx:
                    case 0:
                        if i == 1:
                            self.start += m * 86400
                        else:
                            dt = datetime.datetime.fromtimestamp(self.start, utc)
                            para = [dt.month, dt.day, dt.year, dt.hour]
                            if i == 0:
                                para[i] = (para[i] + m + 11) % 12 + 1
                            elif i == 3:
                                para[i] = (para[i] + m + 24) % 24
                            else:
                                para[i] += m
                            self.start = datetime.datetime(para[2], para[0], para[1], para[3], tzinfo=utc).timestamp()
                    case 1:
                        if i == 1:
                            self.end += m * 86400
                        else:
                            dt = datetime.datetime.fromtimestamp(self.end, utc)
                            para = [dt.month, dt.day, dt.year, dt.hour]
                            if i == 0:
                                para[i] = (para[i] + m + 11) % 12 + 1
                            elif i == 3:
                                para[i] = (para[i] + m + 24) % 24
                            else:
                                para[i] += m
                            self.end = datetime.datetime(para[2], para[0], para[1], para[3], tzinfo=utc).timestamp()
                    case 2:
                        self.step += m * (86400, 3600)[i]
                self.start = max(self.start, 0)
                self.step = max(self.step, 86400)
                self.end = max(self.end, 0)
    
            clickIdx = -1
            surf.blit(font.render("Events: Add    Remove    Save", True, (0, 0, 0)), (270, 7 * self.line - 8))
            for t in range(3):
                bx = 340 + t * 55
                by = 5 + 8 * self.line
                pygame.draw.rect(surf, (0, 0, 0), (bx - 10, by - 10, 20, 20), 2, 4)
                symbol = "+-S"[t]
                o = 2 if t == 2 else 0
                surf.blit(font.render(symbol, True, (0, 0, 0)), (bx - 3 - o, by - 13 + o))
                if abs(relx - bx) < 10 and abs(rely - by) < 10:
                    clickIdx = t
            if inHand.getMouse(0, "press") and clickIdx != -1:
                if clickIdx == 0:
                    print("Name Of Event: ")
                    name = input()
                    desc = ""
                    print("Description: ")
                    while True:
                        add = input()
                        if add == "":
                            break
                        desc += add + '\n'
                    index = self.loader.search(time.time() - 2073600)
                    self.loader.events = self.loader.events[index:]
                    self.loader.events.insert(self.loader.search(self.end), [self.end, self.start, self.step, name, desc[:-1], 0])
                elif clickIdx == 1:
                    if 0 <= self.selected < len(events):
                        self.loader.events.pop(events[self.selected][1])
                elif clickIdx == 2:
                    self.loader.save_events()
    
            return surf
    
    # ============ PRIMARY PYGAME LOOP ============ 
    
    pygame.init()
    pygame.font.init()
    high_score = 0
    score = 0
    fps = 60
    fpsClock = pygame.time.Clock()
    
    width, height = 840, 480
    screen = pygame.display.set_mode((width, height))
    pygame.display.set_caption("Calendar App")
    
    font = pygame.font.SysFont("comicsans", 25)
    inHand = InputHandler()
    calendar = Calendar("event.txt")
    
    # print(calendar.loader.events)
    
    # Main Game Loop
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                running = False
    
        if not running:
            break
        inHand.update()
    
        screen.fill((255, 255, 255))
    
        clickIdx = (-1, -1)
        screen.blit(font.render("Calendar Date Controller", True, (0, 0, 0)), (630, 18))
        for i in range(3):
            by = 55 + i * 25
            label = ("Year", "Month", "Day")[i]
            screen.blit(font.render(f"{label}: ", True, (0, 0, 0)), (630, by - 12))
            for t in range(2):
                bx = 700 + t * 25
                pygame.draw.rect(screen, (0, 0, 0), (bx - 10, by - 10, 20, 20), 2, 4)
                symbol = "+" if t == 0 else "-"
                screen.blit(font.render(symbol, True, (0, 0, 0)), (bx - 3, by - 13))
                if abs(inHand.mousePos[0] - bx) < 10 and abs(inHand.mousePos[1] - by) < 10:
                    clickIdx = (i, t)
        if inHand.getMouse(0, "press") and clickIdx[0] != -1:
            m = (1, -1)[clickIdx[1]]
            i = clickIdx[0]
            if i == 2:
                calendar.date += m * 86400
            else:
                dt = datetime.datetime.fromtimestamp(calendar.date, utc)
                para = [dt.year, dt.month, dt.day]
                if i == 1:
                    para[i] = (para[i] + m + 11) % 12 + 1
                else:
                    para[i] += m
                calendar.date = datetime.datetime(para[0], para[1], para[2], tzinfo=utc).timestamp()
            calendar.date = max(calendar.date, 0)
    
        screen.blit(calendar.render(font, inHand, (20, 20)), (20, 20))
    
        pygame.display.flip()
        fpsClock.tick(fps)
    
    # Closed the game
    print("App Closed Successfully")
    

    How to use

    How To Run
    1. Open trinket.io/pygame
    2. Paste code from “Single File Full Code” into editor
    3. Create new file called “event.txt”
    4. Run the code
    App Manual

    Add Event

    1. Set Start, End, and Step (Using Up/Down Arrow Keys and on-screen [+][-] buttons)
    2. Use Add [+] Button
    3. Fill in the name and description in python console.

    Remove Event

    1. Click on Event from the left view.
    2. Use Remove [-] Button

    Change View Date

    1. Use the right “Calendar Date Controller” to increment or decrement the months, year, or day of viewing.

    Save Changes

    1. Use Save [S] Button

    View More Details

    1. Click on Event from the left view.
    2. A more detailed description of the event will appear in the middle right of the cyan part.

    Mark/Unmark Event

    1. Click on Event from the left view.
    2. Press “f” to toggle Finished/Unfinished
    Demo

    https://drive.google.com/file/d/188g953LIu57BodflmnT3jkoBiPxKbs2b/view

    AI usage

    I did not use AI for this assignment.