איך להעלות תוך כמה שעות תוכנות פייתון לכל העולם בחינם

Profile Picture

מאז שישי האחרון העלתי לרשת כבר יותר משבע תוכנות שמאות משתמשים ניסו. למשל, התוכנה שאתם רואים בסרטון זמינה מהטלפון או מהמחשב שלכם בקישור הבא: dorpascal.com/8-queens

חיפשתי דרך לעשות את זה עם מעט ידע בפרונט, בלי לכתוב מחדש בסיסי קוד, ובלי לשלם על שרתים. מצאתי פתרון וכתבתי לכם אותו כמדריך מקוצר שלא מצאתי בשום מקום ברשת. אלה הצעדים בתיאור כללי, וצריך בשבילם ידע מקדים בפייתון. לפרטים נוספים אתם מוזמנים לקרוא את קבצי המקור שצירפתי באתר של כל תוכנה.

  1. תשיגו דומיין חינמי
    סטודנטים יכולים לקבל דומיין כמו dorpascal.com דרך Namecheap. תגדירו אותו לפי המפרט של GitHub Pages או שתשתמשו ב- itch.io, שזאת פלטפורמה למפתחי אינדי. למשל, https://dor-sketch.itch.io
  2. תבנו ממשק משתמש עם Pygame
    זאת ספריה חינמית למפתחי משחקים, אבל לא רק. תתאימו את הקוד לריצה ברשת בעזרת הספרייה asyncio: תהפכו פונקציות שאחראיות על קלט פלט ל-async דרך הוספת המילה הזאת להגדרה שלהן, ותשתמשו ב-await בקריאות אליהן.
  3. לבסיסי נתונים - תפתחו פרויקט ב-Firebase
    זאת פלטפורמת backend של גוגל. תפתחו חשבון, תגדירו פרויקט חדש, ותצרו Firestore Database (חינמי בסקייל קטן, להבנתי). משם תגדירו את הכללים של בסיס הנתונים שלכם דרך ה-API לניהול התקשורת בהמשך.
  4. תארזו את הקוד כ-WebAssembly בעזרת PygBag
    תריצו pygbag <project dir>, ותראו שני קבצים שנוצרים ממנה: index.html ו-apk. בשלב הזה אתם כבר יכולים להשתמש בתוכנה מתוך הדפדפן שלכם בבית. אתם גם יכולים להריץ עליה טסטים דרך localhost.
  5. בשביל לחבר את התוכנה לבסיס הנתונים תשתמשו xn---platform-4dl.window(<js>) כדי לקרוא ולכתוב ל-local storage. מחוץ לתוכנה, תאזינו למשתנים שאתם רוצים, ותוסיפו סקריפטים לשאילתות. שימו לב שערוץ התקשורת חשוף למשתמשים, ושהפתרון הזה לא מתאים לבסיסי נתונים עם מידע רגיש.
  6. עלו לאוויר
    תריצו pygbag --archive ותשמרו את הקובץ שנוצר ב-GitHub Pages או ב- itch.io.
#OpenToWork #Python #WebAssembly #Databases

Published on LinkedIn on June 13, 2024

Video Placeholder
Video Placeholder

נסו את התוכנה בעצמכם

קוד המקור

"""
This file contains the graphical user interface for the 8 Queens Problem.
"""
import random
import pygame
import asyncio
import platform
import json

SHADOW_COLOR = (0, 0, 100, 10)
LIGHT_CELL_COLOR = [125, 135, 150]
DARK_CELL_COLOR = [233, 236, 239]
BORDER_COLOR = (0, 90, 146)
BOARD_SIZE = 8

pygame.font.init()
font = pygame.font.Font('NotoSansSymbols2-Regular.ttf', 36)
timer_font = pygame.font.Font('SpaceMono-Regular.ttf', 36)

if 'window' in platform.__dict__:
    platform.window.eval(f"localStorage.setItem('end', 'false');")

def load_and_scale_image(image_path, target_size, fill_color=(0, 0, 0, 0)):
    image = pygame.image.load(image_path)
    width, height = image.get_size()
    scale_factor = min(target_size / width, target_size / height)
    image = pygame.transform.scale(image, (int(width * scale_factor), int(height * scale_factor)))
    surface = pygame.Surface((target_size, target_size), pygame.SRCALPHA)
    surface.fill(fill_color)
    width, height = image.get_size()
    surface.blit(image, ((target_size - width) // 2, (target_size - height) // 2))
    return surface

class GameGUI:
    def __init__(self):
        pygame.init()
        pygame.mixer.init()
        pygame.mixer.music.load('fable.mp3')
        pygame.mixer.music.play(-1)
        pygame.mixer.music.set_endevent(pygame.USEREVENT)
        self.threats = 0
        self.board = [[0 for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)]
        self.board_size = 800
        self.cell_size = self.board_size // BOARD_SIZE
        self.screen = pygame.display.set_mode((self.board_size + self.cell_size, self.board_size + self.cell_size))
        self.clock = pygame.time.Clock()
        self.indices = None
        self.diag_indices = None
        self.queen_image = load_and_scale_image('queen_w.png', self.cell_size + 20)
        self.dark_queen_image = load_and_scale_image('queen_b.png', self.cell_size + 20)
        self.init_cell_sizes()
        pygame.display.set_caption(f'{BOARD_SIZE} Queens Problem')
        self.done_x_pos = 0
        self.directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
        self.direction = random.choice(self.directions)
        self.pos = [0, 0]
        self.speed = 1
        self.last_time = pygame.time.get_ticks()
        self.selected_queen = None
        self.missing_queens = BOARD_SIZE
        self.dirty = True

    def init_cell_sizes(self):
        self.half_cell_size = self.cell_size // 2
        self.sixth_cell_size = self.cell_size // 6
        self.eight_cell_size = self.cell_size // 8

    def calc_threats(self, board, x, y):
        threats = 0
        directions = [(0, 1), (1, 0), (0, -1), (-1, 0), (1, 1), (-1, -1), (1, -1), (-1, 1)]
        for direction in directions:
            i, j = x, y
            while True:
                i += direction[0]
                j += direction[1]
                if i < 0 or i >= len(board) or j < 0 or j >= len(board[0]):
                    break
                if board[i][j] == 1:
                    threats += 1
        return threats

    def draw_board(self, board=None):
        if not self.dirty:
            return
        if board is None:
            board = self.board

        cell_size = self.cell_size
        screen = self.screen
        game_size = BOARD_SIZE
        threats = 0
        for i in range(game_size):
            for j in range(game_size):
                color = DARK_CELL_COLOR if (i + j) % 2 == 0 else LIGHT_CELL_COLOR
                pygame.draw.rect(screen, color, pygame.Rect(j * cell_size, i * cell_size + cell_size, cell_size, cell_size))

        for i in range(game_size):
            for j in range(game_size):
                if board[i][j] == 1:
                    cur_threats = self.calc_threats(board, i, j)
                    if cur_threats > 0:
                        threats += cur_threats
                        self.draw_queen(i, j, cell_size, False)
                    else:
                        self.draw_queen(i, j, cell_size)
        self.threats = threats
        self.draw_stats(screen, threats)
        self.dirty = False

    def draw_stats(self, screen, threats):
        threats_icon = font.render('☠', True, (255, 255, 255))
        queen_icon = font.render('♛', True, (255, 255, 255))
        threats = timer_font.render(str(threats), True, (255, 255, 255))
        queen = timer_font.render(str(8 - self.missing_queens) + '/8', True, (255, 255, 255))
        screen.blit(threats_icon, (240, 0))
        screen.blit(threats, (280, 0))
        screen.blit(queen_icon, (380, 0))
        screen.blit(queen, (420, 0))

    def draw_timer(self, screen, time):
        clock_text = font.render('⏱', True, (255, 255, 255), BORDER_COLOR)
        screen.blit(clock_text, (0, 0))
        minutes, seconds = divmod(time, 60)
        if minutes < 1:
            time_text = f'{time:4.2f}'
        else:
            time_text = f'{int(minutes)}:{seconds:05.2f}'
        time_text = timer_font.render(time_text, True, (255, 255, 255), BORDER_COLOR)
        textpos = time_text.get_rect(topleft=(50, 0))
        screen.blit(time_text, textpos)

    def draw_missing_queens(self):
        cell_size = self.cell_size
        pygame.draw.rect(self.screen, BORDER_COLOR, pygame.Rect(self.screen.get_width() - cell_size, 0, cell_size, self.screen.get_height()))
        pygame.draw.rect(self.screen, BORDER_COLOR, pygame.Rect(0, 0, self.screen.get_width(), cell_size))

        missing_queens = self.missing_queens
        screen_width, screen_height = self.screen.get_size()
        for x in range(BOARD_SIZE + 1):
            for y in range(BOARD_SIZE + 1):
                if missing_queens == 0:
                    return
                for i in range(self.cell_size * self.missing_queens):
                    color = (i * 255 // screen_height, i * 255 // screen_height, 255)
                    pygame.draw.line(self.screen, color, (screen_width - cell_size, i + cell_size), (screen_width, i))
                    shadow_color = (max(0, color[0] - 50), max(0, color[1] - 50), max(0, color[2] - 50))
                    pygame.draw.line(self.screen, shadow_color, (screen_width - cell_size + 1, i + 1 + cell_size), (screen_width + 1, i + 1))
                missing_queens -= 1

    def draw_queen(self, x, y, cell_size, light=True):
        pos_x = y * cell_size - 10
        pos_y = x * cell_size - 40
        queen = self.queen_image if light else self.dark_queen_image
        self.screen.blit(queen, (pos_x, pos_y + cell_size))

    async def draw_done(self, done_surface, screen):
        done_surface.fill((0, 0, 0, 0))
        done_text = font.render('Well Done!', True, (255, 255, 255), BORDER_COLOR)
        textpos = done_text.get_rect(topleft=(240, 0))
        done_surface.blit(done_text, textpos)
        smile = [
            ".*....*.",
            "*.*..*.*",
            "........",
            "........",
            ".*....*.",
            "..*..*..",
            "...**...",
            "........"
        ]
        for i in range(8):
            for j in range(8):
                if smile[i][j] == '*':
                    pygame.draw.rect(done_surface, (0, 255, 0), pygame.Rect(j * self.cell_size, (i + 1) * self.cell_size, self.cell_size, self.cell_size))
        screen.blit(done_surface, (0, 0))

    def handle_events(self):
        board_size = BOARD_SIZE
        cell_size = self.screen.get_width() // (board_size + 1)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return False
            elif event.type == pygame.MOUSEBUTTONDOWN:
                x, y = pygame.mouse.get_pos()
                if 0 <= x <= board_size * cell_size and cell_size <= y <= board_size * cell_size + cell_size:
                    row, col = y // cell_size, x // cell_size
                    row -= 1
                    if self.missing_queens > 0 and self.board[row][col] == 0:
                        self.board[row][col] = 1
                        self.missing_queens -= 1
                        self.dirty = True
                        break
                    elif self.board[row][col] == 1 and self.missing_queens < board_size:
                        self.missing_queens += 1
                        self.board[row][col] = 0
                        self.dirty = True
                        break
        return True

    async def run(self):
        self.player_turn = True
        running = True
        self.draw_board(self.board)
        self.draw_missing_queens()
        self.draw_stats(self.screen, 0)
        screen = self.screen
        timer = 0
        pygame.display.flip()
        while running:
            missing_flag = self.missing_queens > 0
            self.clock.tick(60)
            self.draw_board(self.board)
            pygame.display.flip()
            prev_missing_queens = self.missing_queens
            running = self.handle_events()
            if prev_missing_queens != self.missing_queens:
                self.draw_missing_queens()
            timer += self.clock.get_time() / 1000
            self.draw_timer(screen, timer)
            await asyncio.sleep(0)
            if not missing_flag and self.threats == 0:
                break

        if running:
            done_text = timer_font.render('Well Done!', True, (255, 255, 255), BORDER_COLOR)
            screen.blit(done_text, done_text.get_rect(topleft=(240, 0)))
            WIDTH, HEIGHT = screen.get_size()
            if 'window' in platform.__dict__:
                platform.window.eval(f"localStorage.setItem('time', '{int(timer)}');")
                platform.window.eval("localStorage.setItem('end', 'true');")
                dark_surface = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
                dark_surface.fill((0, 0, 0, 100))
                screen.blit(dark_surface, (0, 0))
                topTimes = platform.window.eval("localStorage.getItem('topTimes');")
                topTimes = json.loads(topTimes)
                font = pygame.font.Font(None, 36)
                time_COLOR = (253, 208, 23)
                text = font.render("Top Solvers Times", True, time_COLOR)
                x = WIDTH // 2 - text.get_width() // 2
                screen.blit(text, text.get_rect(topleft=(x, 40)))
                y = 90
                x -= 40
                max_name_length = max([len(line.split(":")[0]) for line in topTimes])
                for line in topTimes:
                    name, time = line.split(":")
                    text = font.render(name, True, time_COLOR)
                    screen.blit(text, text.get_rect(topleft=(x, y)))
                    time_x = x + (max_name_length * font.size(" ")[0] * 2)
                    text = font.render(time, True, time_COLOR)
                    screen.blit(text, text.get_rect(topleft=(time_x, y)))
                    y += 50
                pygame.display.flip()
                pygame.display.fill((255, 255, 255))
                pygame.display.flip()

            done_surface = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
            while running:
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        running = False
                self.draw_board(self.board)
                await self.draw_done(done_surface, screen)
                game_size = BOARD_SIZE
                board = self.board
                cell_size = self.cell_size
                for i in range(game_size):
                    for j in range(game_size):
                        if board[i][j] == 1:
                            self.draw_queen(i, j, cell_size)
                self.dirty = False
                pygame.display.flip()
                await asyncio.sleep(1)
        pygame.quit()

async def main():
    gui = GameGUI()
    await gui.run()

if __name__ == "__main__":
    asyncio.run(main())