0% found this document useful (0 votes)
11 views9 pages

Killer Sudoku Game with AI Integration

Uploaded by

aadii.apurba.g
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views9 pages

Killer Sudoku Game with AI Integration

Uploaded by

aadii.apurba.g
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

import tkinter as tk

from tkinter import ttk, messagebox, simpledialog


import random
import time
import json
import [Link]
import [Link]
import threading

# --- CONFIGURATION ---


# PASTE YOUR GEMINI API KEY HERE
API_KEY = ""

MODEL_NAME = "gemini-2.5-flash-preview-09-2025"
API_URL =
f"[Link]
ent"

# Colors
CAGE_COLORS = [
'#ffb3ba', '#ffdfba', '#ffffba', '#baffc9', '#bae1ff', '#e6baff',
'#f0e68c', '#e0ffff', '#ffe4e1', '#f0fff0', '#e6e6fa', '#fff0f5',
'#f5f5dc', '#dcdcdc', '#98fb98', '#ffc0cb', '#87cefa'
]
COLOR_BG = "#f8fafc"
COLOR_GRID = "#334155"
COLOR_DASH = "#475569"
COLOR_SELECT = "#94a3b8"
COLOR_ERROR = "#fecaca"
COLOR_TEXT = "#1e293b"
COLOR_NOTE = "#64748b"

class SudokuLogic:
def __init__(self):
[Link] = []
[Link] = []
[Link] = [] # List of {sum, cells: [indices], color}
[Link] = {} # Map index -> set of numbers

def generate_game(self, difficulty="Medium"):


# 1. Generate Solution
[Link] = [0] * 81
self.fill_grid([Link])

# 2. Generate Cages
[Link] = self.generate_cages([Link])

# 3. Create Puzzle (Remove numbers)


[Link] = [0] * 81
[Link] = {i: set() for i in range(81)}

givens_count = {
"Easy": 38,
"Medium": 26,
"Hard": 14,
"Expert": 0
}.get(difficulty, 26)

indices = list(range(81))
[Link](indices)

for i in range(givens_count):
idx = indices[i]
[Link][idx] = [Link][idx]

def fill_grid(self, grid):


empty = -1
for i in range(81):
if grid[i] == 0:
empty = i
break
if empty == -1: return True

row, col = divmod(empty, 9)


nums = list(range(1, 10))
[Link](nums)

for num in nums:


if self.is_valid(grid, row, col, num):
grid[empty] = num
if self.fill_grid(grid):
return True
grid[empty] = 0
return False

def is_valid(self, grid, row, col, num):


# Row check
for c in range(9):
if grid[row * 9 + c] == num: return False
# Col check
for r in range(9):
if grid[r * 9 + col] == num: return False
# Box check
start_r, start_c = (row // 3) * 3, (col // 3) * 3
for r in range(3):
for c in range(3):
if grid[(start_r + r) * 9 + (start_c + c)] == num: return False
return True

def generate_cages(self, grid):


cages = []
visited = [False] * 81
color_idx = 0

while False in visited:


start = [Link](False)

# Determine size (biased towards 2 and 3)


r = [Link]()
if r < 0.1: size = 1
elif r < 0.4: size = 2
elif r < 0.7: size = 3
elif r < 0.9: size = 4
else: size = 5

current_cells = [start]
visited[start] = True
# Grow cage
for _ in range(size - 1):
candidates = []
for cell_idx in current_cells:
cr, cc = divmod(cell_idx, 9)
neighbors = [(cr-1, cc), (cr+1, cc), (cr, cc-1), (cr, cc+1)]
for nr, nc in neighbors:
if 0 <= nr < 9 and 0 <= nc < 9:
n_idx = nr * 9 + nc
if not visited[n_idx] and n_idx not in current_cells:
[Link](n_idx)

if candidates:
next_cell = [Link](candidates)
current_cells.append(next_cell)
visited[next_cell] = True
else:
break

cage_sum = sum(grid[i] for i in current_cells)


[Link]({
"sum": cage_sum,
"cells": current_cells,
"color": CAGE_COLORS[color_idx % len(CAGE_COLORS)],
"id": len(cages)
})
color_idx += 1

return cages

class KillerSudokuApp([Link]):
def __init__(self):
super().__init__()
[Link]("Python Killer Sudoku + Gemini AI")
[Link]("600x850")
[Link](bg=COLOR_BG)

[Link] = SudokuLogic()
self.selected_cell = -1
self.is_note_mode = False
[Link] = 0
self.timer_seconds = 0
self.timer_running = False
[Link] = []

# UI Setup
self.setup_ui()
self.start_new_game()

def setup_ui(self):
# --- Top Controls ---
top_frame = [Link](self, bg=COLOR_BG)
top_frame.pack(pady=10, fill='x', padx=20)

[Link](top_frame, text="Difficulty:", bg=COLOR_BG).pack(side='left')


self.diff_var = [Link](value="Medium")
diff_menu = [Link](top_frame, self.diff_var, "Medium", "Easy",
"Medium", "Hard", "Expert")
diff_menu.pack(side='left', padx=5)
[Link](top_frame, text="New Game",
command=self.start_new_game).pack(side='left', padx=10)

self.stats_label = [Link](top_frame, text="Time: 00:00 | Mistakes: 0/3",


bg=COLOR_BG, font=("Arial", 10, "bold"))
self.stats_label.pack(side='right')

# --- Canvas ---


self.canvas_size = 500
self.cell_size = self.canvas_size / 9
[Link] = [Link](self, width=self.canvas_size,
height=self.canvas_size, bg="white", highlightthickness=0)
[Link](pady=10)
[Link]("<Button-1>", self.on_canvas_click)

# --- Numpad & Tools ---


controls_frame = [Link](self, bg=COLOR_BG)
controls_frame.pack(pady=10)

# Row 1: Undo, 1-3, Erase


btn_grid = [Link](controls_frame, bg=COLOR_BG)
btn_grid.pack()

self.create_btn(btn_grid, "Undo", [Link], 0, 0, width=6)


for i in range(1, 4): self.create_btn(btn_grid, str(i), lambda n=i:
self.input_number(n), 0, i)
self.create_btn(btn_grid, "Erase", lambda: self.input_number(0), 0, 4,
width=6)

# Row 2: Note, 4-6, Hint


self.note_btn = self.create_btn(btn_grid, "Note: Off", self.toggle_note, 1,
0, width=8)
for i in range(4, 7): self.create_btn(btn_grid, str(i), lambda n=i:
self.input_number(n), 1, i)
self.create_btn(btn_grid, "✨Hint", self.ask_gemini_hint, 1, 4, width=6,
bg="#e0e7ff")

# Row 3: Space, 7-9, Cage


[Link](btn_grid, bg=COLOR_BG, width=6).grid(row=2, column=0)
for i in range(7, 10): self.create_btn(btn_grid, str(i), lambda n=i:
self.input_number(n), 2, i)
self.create_btn(btn_grid, "✨Cage", self.ask_gemini_cage, 2, 4, width=6,
bg="#e0e7ff")

# Key bindings
[Link]("<Key>", self.on_key_press)

def create_btn(self, parent, text, cmd, r, c, width=4, bg="white"):


btn = [Link](parent, text=text, command=cmd, width=width, height=2,
bg=bg, relief="groove", borderwidth=1)
[Link](row=r, column=c, padx=3, pady=3)
return btn

def start_new_game(self):
[Link].generate_game(self.diff_var.get())
self.selected_cell = -1
[Link] = 0
self.timer_seconds = 0
[Link] = []
self.update_stats()
self.draw_board()

if not self.timer_running:
self.timer_running = True
self.update_timer()

def update_timer(self):
if self.timer_running:
self.timer_seconds += 1
self.update_stats()
[Link](1000, self.update_timer)

def update_stats(self):
mins, secs = divmod(self.timer_seconds, 60)
self.stats_label.config(text=f"Time: {mins:02}:{secs:02} | Mistakes:
{[Link]}/3")

# --- Drawing ---


def draw_board(self):
[Link]("all")

# 1. Draw Cell Backgrounds (Cages)


cell_to_cage = {}
for cage in [Link]:
for cell_idx in cage['cells']:
cell_to_cage[cell_idx] = cage
r, c = divmod(cell_idx, 9)
x0, y0 = c * self.cell_size, r * self.cell_size
x1, y1 = x0 + self.cell_size, y0 + self.cell_size
[Link].create_rectangle(x0, y0, x1, y1, fill=cage['color'],
outline="")

# 2. Draw Dashed Cage Borders


for r in range(9):
for c in range(9):
idx = r * 9 + c
cage_id = cell_to_cage[idx]['id']
x, y = c * self.cell_size, r * self.cell_size

# Check Right
if c < 8 and cell_to_cage[r * 9 + c + 1]['id'] != cage_id:
[Link].create_line(x + self.cell_size, y, x +
self.cell_size, y + self.cell_size,
fill=COLOR_DASH, width=2, dash=(4, 4))
# Check Bottom
if r < 8 and cell_to_cage[(r + 1) * 9 + c]['id'] != cage_id:
[Link].create_line(x, y + self.cell_size, x +
self.cell_size, y + self.cell_size,
fill=COLOR_DASH, width=2, dash=(4, 4))

# 3. Draw Grid Lines


for i in range(10):
width = 3 if i % 3 == 0 else 1
# Vertical
[Link].create_line(i * self.cell_size, 0, i * self.cell_size,
self.canvas_size, fill=COLOR_GRID, width=width)
# Horizontal
[Link].create_line(0, i * self.cell_size, self.canvas_size, i *
self.cell_size, fill=COLOR_GRID, width=width)

# 4. Draw Numbers and Notes


for i in range(81):
r, c = divmod(i, 9)
val = [Link][i]
x, y = c * self.cell_size, r * self.cell_size
center_x, center_y = x + self.cell_size/2, y + self.cell_size/2

# Selection Highlight
if i == self.selected_cell:
[Link].create_rectangle(x+2, y+2, x+self.cell_size-2,
y+self.cell_size-2,
outline=COLOR_SELECT, width=3)

# Draw Cage Sum (if top-left of cage)


cage = cell_to_cage[i]
if i == min(cage['cells']):
[Link].create_text(x + 4, y + 4, text=str(cage['sum']),
anchor="nw",
font=("Arial", 8, "bold"), fill="#000")

if val != 0:
color = COLOR_TEXT
if val != [Link][i]:
color = "red" # Simply show red for wrong
[Link].create_text(center_x, center_y, text=str(val),
font=("Arial", 20), fill=color)
else:
# Draw Notes
notes = [Link][i]
for n in notes:
nx = x + (10 + ((n-1)%3)*15)
ny = y + (10 + ((n-1)//3)*15)
[Link].create_text(nx, ny, text=str(n), font=("Arial", 8),
fill=COLOR_NOTE)

# --- Interaction ---


def on_canvas_click(self, event):
c = int(event.x // self.cell_size)
r = int(event.y // self.cell_size)
if 0 <= c < 9 and 0 <= r < 9:
self.selected_cell = r * 9 + c
self.draw_board()

def on_key_press(self, event):


if self.selected_cell == -1: return
char = [Link]
if char in "123456789":
self.input_number(int(char))
elif [Link] in ["BackSpace", "Delete"]:
self.input_number(0)
elif [Link]() == 'n':
self.toggle_note()

# Arrow keys
r, c = divmod(self.selected_cell, 9)
if [Link] == "Up": r = max(0, r-1)
elif [Link] == "Down": r = min(8, r+1)
elif [Link] == "Left": c = max(0, c-1)
elif [Link] == "Right": c = min(8, c+1)
self.selected_cell = r * 9 + c
self.draw_board()

def toggle_note(self):
self.is_note_mode = not self.is_note_mode
txt = "Note: On" if self.is_note_mode else "Note: Off"
bg = COLOR_TEXT if self.is_note_mode else "white"
fg = "white" if self.is_note_mode else "black"
self.note_btn.config(text=txt, bg=bg, fg=fg)

def input_number(self, num):


if self.selected_cell == -1: return

if self.is_note_mode:
if num == 0:
[Link][self.selected_cell].clear()
else:
s = [Link][self.selected_cell]
if num in s: [Link](num)
else: [Link](num)
else:
prev = [Link][self.selected_cell]
if prev == num: return

# Basic history
[Link]((self.selected_cell, prev,
set([Link][self.selected_cell])))

[Link][self.selected_cell] = num

# Check correctness immediately


if num != 0 and num != [Link][self.selected_cell]:
[Link] += 1
self.update_stats()
if [Link] >= 3:
[Link]("Game Over", "Too many mistakes!")
self.timer_running = False

# Check win
if 0 not in [Link]:
if [Link] == [Link]:
self.timer_running = False
[Link]("Victory", f"Solved in
{self.stats_label.cget('text')}")

self.draw_board()

def undo(self):
if not [Link]: return
idx, prev_val, prev_notes = [Link]()
[Link][idx] = prev_val
[Link][idx] = prev_notes
self.draw_board()

# --- GEMINI API ---


def check_api_key(self):
global API_KEY
if not API_KEY:
key = [Link]("API Key Required", "Please enter your
Gemini API Key:\n(See code to hardcode it)")
if key:
API_KEY = key
return True
return False
return True

def call_gemini(self, prompt, callback):


if not self.check_api_key(): return

def run_thread():
try:
data = {
"contents": [{"parts": [{"text": prompt}]}]
}
req = [Link](
f"{API_URL}?key={API_KEY}",
data=[Link](data).encode('utf-8'),
headers={'Content-Type': 'application/json'}
)
with [Link](req) as response:
res_json = [Link](response)
text = res_json['candidates'][0]['content']['parts'][0]['text']
[Link](0, lambda: callback(text))
except Exception as e:
[Link](0, lambda: [Link]("API Error", str(e)))

[Link](target=run_thread, daemon=True).start()

def ask_gemini_hint(self):
if self.selected_cell == -1:
[Link]("Hint", "Select a cell first!")
return

cage = next((c for c in [Link] if self.selected_cell in


c['cells']), None)
filled_vals = [[Link][i] for i in cage['cells']]

prompt = f"""
I am playing Killer Sudoku.
I am stuck on cell index {self.selected_cell}.
This cell belongs to a Cage with Sum: {cage['sum']}.
Current values in this cage (0 is empty): {filled_vals}.
The correct value for this cell is:
{[Link][self.selected_cell]}.

Give me a logical hint for this cell. Explain the logic based on the Cage
Sum math.
Keep it short (max 2 sentences). Don't just give the answer.
"""

self.call_gemini(prompt, lambda res: [Link]("✨ Smart Hint",


res))

def ask_gemini_cage(self):
if self.selected_cell == -1:
[Link]("Cage Help", "Select a cell first!")
return

cage = next((c for c in [Link] if self.selected_cell in


c['cells']), None)

prompt = f"""
I am playing Killer Sudoku.
I selected a Cage with Sum {cage['sum']} that has {len(cage['cells'])}
cells.

List ALL valid mathematical combinations of {len(cage['cells'])} unique


digits (1-9) that sum to {cage['sum']}.
Format as a clean list (e.g. "1 + 4").
"""

self.call_gemini(prompt, lambda res: [Link]("✨ Cage Math",


res))

if __name__ == "__main__":
app = KillerSudokuApp()
[Link]()

You might also like