import pygame as pg from pygame import Rect, Vector2 as v2 from dataclasses import dataclass, field from enum import Enum from typing import Set import threading import os from websudoku import * @dataclass class Cursor: row: int = 5 col: int = 5 @dataclass class Cell: value: int = 0 given: bool = False @dataclass class PencilMark: center: Set[int] = field(default_factory=set) border: Set[int] = field(default_factory=set) class InputMethod(Enum): """ This enum helps representing Snyder notation in the cells """ FILL = 1, CENTER = 2, BORDER = 3 ################## # PYGAME INIT ################## oswinx, oswiny = 1940,50 os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (oswinx,oswiny) pg.init() screen = pg.display.set_mode((900, 900)) pg.display.set_caption("Pydoku") pg.key.set_repeat(200, 35) clock = pg.time.Clock() running = True ################## # FONTS ################## title_font = pg.font.Font(None, 64) filled_num_font = pg.font.Font(None, 60) btn_font = pg.font.Font(None, 28) pm_border_font = pg.font.Font(None, 18) pm_center_font = pg.font.Font(None, 32) title = title_font.render("Pydoku", True, "black") ################## # GAME ################## CELL_SIZE = 60 CURSOR_SIZE = CELL_SIZE + 6 GRID_X = screen.get_width() / 2 - 9 * CELL_SIZE // 2 GRID_Y = screen.get_height() / 2 - 9 * CELL_SIZE // 2 cursorSurface = pg.Surface((CURSOR_SIZE, CURSOR_SIZE), pg.SRCALPHA) cursor = Cursor() input_method = InputMethod.FILL board = [] pencil_marks = [] sudoku = None data_lock = threading.Lock() def data_callback(sudoku_data): # Handle the data here, e.g., store it in a global variable for i in range(81): if sudoku_data.editmask[i] == 0: board.append(Cell(sudoku_data.solution[i], True)) else: board.append(Cell()) for i in range(81): pm = PencilMark() if not board[i].given else None pencil_marks.append(pm) global sudoku with data_lock: sudoku = sudoku_data def http_request_thread(): data = get_sudoku_puzzle(Difficulty.EVIL) data_callback(data) http_thread = threading.Thread(target=http_request_thread) http_thread.start() def draw_grid(): for row in range(9): for col in range(9): rect = Rect(CELL_SIZE * col + GRID_X, CELL_SIZE * row + GRID_Y, CELL_SIZE, CELL_SIZE) rect = Rect(CELL_SIZE * col + GRID_X, CELL_SIZE * row + GRID_Y, CELL_SIZE, CELL_SIZE) pg.draw.rect(screen, "white", rect) pg.draw.rect(screen, "gray", rect, width=1) for l in range(4): pg.draw.line(screen, "black", start_pos=(GRID_X, GRID_Y + CELL_SIZE * 3 * l), end_pos =(GRID_X + CELL_SIZE * 9, GRID_Y + CELL_SIZE * 3 * l), width=2) for l in range(4): pg.draw.line(screen, "black", start_pos=(GRID_X + CELL_SIZE * 3 * l, GRID_Y), end_pos =(GRID_X + CELL_SIZE * 3 * l, GRID_Y + CELL_SIZE * 9), width=2) def draw_cursor(): pad_diff = (CURSOR_SIZE - CELL_SIZE) // 2 pg.draw.rect(cursorSurface, (100, 0, 155, 100), [0, 0, CURSOR_SIZE, CURSOR_SIZE]) rect = Rect(CELL_SIZE * (cursor.col - 1) + GRID_X - pad_diff, CELL_SIZE * (cursor.row - 1) + GRID_Y - pad_diff, CURSOR_SIZE, CURSOR_SIZE) screen.blit(cursorSurface, rect) def draw_pm_border(row, col, idx): third = CELL_SIZE // 3 for n in pencil_marks[idx].border: digit = pm_border_font.render(str(n), True, "red") padding = 6 basepos = (GRID_X + CELL_SIZE * col + padding, GRID_Y + CELL_SIZE * row + padding) pos = basepos[0] + ((n - 1) % 3) * third , basepos[1] + ((n - 1) // 3) * third screen.blit(digit, pos) def draw_pm_center(row, col, idx): third = CELL_SIZE // 3 nums = "".join(map(str, sorted(pencil_marks[idx].center))) digits = pm_center_font.render(nums, True, "dark green") pos = (GRID_X + CELL_SIZE * col + CELL_SIZE // 2 - digits.get_width() // 2, GRID_Y + CELL_SIZE * row + CELL_SIZE // 2 - digits.get_height() // 2.3) screen.blit(digits, pos) done_txt = btn_font.render("Check", True, "black") done_rect = Rect(screen.get_width() - 10 - 100, 10, 100, 30) def draw_buttons(): pg.draw.rect(screen, "gray", done_rect) pg.draw.rect(screen, "black", done_rect, width=2) screen.blit(done_txt, (done_rect.x + done_rect.w // 2 - done_txt.get_width() // 2, done_rect.y + done_rect.h // 2 - done_txt.get_height() // 2)) def draw_numbers(): for row in range(9): for col in range(9): idx = row * 9 + col cell = board[idx] if cell.value > 0: color = "black" if cell.given else "dark blue" digit = filled_num_font.render(str(cell.value), True, color) pos = (GRID_X + CELL_SIZE * col + CELL_SIZE // 2 - digit.get_width() // 2, # Here we divide the height by 2.3 because there seems to be some extra # padding at the bottom of the surface GRID_Y + CELL_SIZE * row + CELL_SIZE // 2 - digit.get_height() // 2.3) screen.blit(digit, pos) continue if pencil_marks[idx].center: draw_pm_center(row, col, idx) continue if pencil_marks[idx].border: draw_pm_border(row, col, idx) def draw_hud(): screen.blit(title, (screen.get_width() // 2 - title.get_width() // 2, 70)) def check_board(): for i,cell in enumerate(board): if cell.value == 0: print("You still have to finish the puzzle!") return if cell.value != sudoku.solution[i]: print("You made a mistake!") return print("You completed it!") while running: for event in pg.event.get(): if event.type == pg.QUIT: running = False if event.type == pg.MOUSEBUTTONDOWN: if event.button == 1: grid_max_x = GRID_X + CELL_SIZE * 9 grid_max_y = GRID_Y + CELL_SIZE * 9 mx = event.pos[0] my = event.pos[1] if (mx >= GRID_X and mx <= grid_max_x and my >= GRID_Y and my <= grid_max_y): cursor.row = int((my - GRID_Y) // CELL_SIZE + 1) cursor.col = int((mx - GRID_X) // CELL_SIZE + 1) if done_rect.collidepoint(event.pos): check_board() if event.type == pg.KEYDOWN: # keys = pg.key.get_pressed() if (event.key == pg.K_h or event.key == pg.K_a) and cursor.col > 1: cursor.col -= 1 if (event.key == pg.K_k or event.key == pg.K_w) and cursor.row > 1: cursor.row -= 1 if (event.key == pg.K_j or event.key == pg.K_s) and cursor.row < 9: cursor.row += 1 if (event.key == pg.K_l or event.key == pg.K_d) and cursor.col < 9: cursor.col += 1 num = pg.key.name(event.key) if num.isdigit() and num != '0': num = int(num) idx = (cursor.row - 1) * 9 + cursor.col - 1 if board and not board[idx].given: match input_method: case InputMethod.FILL: board[idx].value = num case InputMethod.CENTER: if num in pencil_marks[idx].center: pencil_marks[idx].center.remove(num) # Max 3 center pencil marks elif len(pencil_marks[idx].center) < 3: pencil_marks[idx].center.add(num) case InputMethod.BORDER: if num in pencil_marks[idx].border: pencil_marks[idx].border.remove(num) else: pencil_marks[idx].border.add(num) if event.key == pg.K_BACKSPACE: idx = (cursor.row - 1) * 9 + cursor.col - 1 if not board[idx].given: board[idx].value = 0 match event.key: case pg.K_f: input_method = InputMethod.FILL case pg.K_c: input_method = InputMethod.CENTER case pg.K_b: input_method = InputMethod.BORDER ############## # Debug stuff ############## if event.key == pg.K_F1: print(sudoku) if event.key == pg.K_F2: idx = (cursor.row - 1) * 9 + cursor.col - 1 if not board[idx].given: pencil_marks[idx].border = {i for i in range(1, 10)} if event.key == pg.K_F3: for i,cell in enumerate(board): cell.value = sudoku.solution[i] screen.fill('cornflower blue') draw_grid() with data_lock: if sudoku is not None: draw_numbers() draw_cursor() draw_hud() draw_buttons() pg.display.flip() dt = clock.tick(60) / 1000 pg.quit()