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 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 # TODO: We have to make this async sudoku = get_sudoku_puzzle(Difficulty.EVIL) # sudoku = get_sudoku_puzzle_test() board = [] for i in range(81): if sudoku.editmask[i] == 0: board.append(Cell(sudoku.solution[i], True)) else: board.append(Cell()) pencil_marks = [] for i in range(81): pm = PencilMark() if not board[i].given else None pencil_marks.append(pm) 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 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() draw_numbers() draw_cursor() draw_hud() draw_buttons() pg.display.flip() dt = clock.tick(60) / 1000 pg.quit()