hello!
i’m implementing a sudoku solver using streamlit and i want to display an editable sudoku grid using HTML. however, i cannot manage to update the values of the edited cells of the HTML grid once the Enter key is pressed. i am not familiar with HTML and Javascript, so that part of the code was writen from ChatGPT, but i’m wondering whether such thing is doable in the first place.
appreciate the help!
giulio
import numpy as np
import streamlit as st
def generate_editable_sudoku_html(sudoku: np.ndarray) -> str:
"""Generate an editable HTML representation of a sudoku puzzle."""
style = """
<style>
.sudoku-table {
border-collapse: collapse;
font-family: Calibri, sans-serif;
margin: 0 auto;
}
.sudoku-table tbody {
border: solid 3px black;
}
.sudoku-table td {
border: solid 1px black;
height: 2em;
width: 2em;
padding: 0;
}
.sudoku-input {
width: 100%;
height: 100%;
border: none;
text-align: center;
font-family: Calibri, sans-serif;
font-size: 1.2em;
padding: 0;
margin: 0;
box-sizing: border-box;
}
.sudoku-input:focus {
outline: none;
background-color: #e8f0fe;
}
</style>
"""
script = """
<script>
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.getElementsByClassName('sudoku-input');
for (let input of inputs) {
input.addEventListener('keyup', function(e) {
if (e.key === 'Enter' || this.value.length === 1) {
// Validate input to only allow numbers 1-9
let value = this.value.replace(/[^1-9]/g, '');
if (value.length > 1) value = value[0];
this.value = value;
// Get position data
let row = parseInt(this.getAttribute('data-row'));
let col = parseInt(this.getAttribute('data-col'));
// Save data to localStorage
localStorage.setItem('sudoku_update', JSON.stringify({ row, col, value: value || "0" }));
// Move to next input on Enter
if (e.key === 'Enter') {
let nextCol = col + 1;
let nextRow = row;
if (nextCol > 8) {
nextCol = 0;
nextRow++;
}
if (nextRow > 8) nextRow = 0;
let nextInput = document.querySelector(
`[data-row="${nextRow}"][data-col="${nextCol}"]`
);
if (nextInput) nextInput.focus();
}
}
});
}
});
</script>
"""
table = "<table class='sudoku-table'>"
for i in range(9):
table += "<tr>"
for j in range(9):
# Add thicker borders for 3x3 grid
border_style = []
if (i + 1) % 3 == 0 and i < 8:
border_style.append("border-bottom: 3px solid black")
if (j + 1) % 3 == 0 and j < 8:
border_style.append("border-right: 3px solid black")
cell_style = "; ".join(border_style)
value = str(sudoku[i, j]) if sudoku[i, j] != 0 else ""
table += f"""
<td style="{cell_style}">
<input
type="text"
class="sudoku-input"
value="{value}"
data-row="{i}"
data-col="{j}"
maxlength="1"
pattern="[1-9]"
>
</td>
"""
table += "</tr>"
table += "</table>"
return f"<div style='text-align: center'>{style}{table}{script}</div>"
def main():
st.title("Editable Sudoku")
# Initialize session state for the sudoku grid
if 'sudoku_state' not in st.session_state:
st.session_state.sudoku_state = np.zeros((9, 9), dtype=int)
# Create the editable grid
grid_html = generate_editable_sudoku_html(st.session_state.sudoku_state)
poll_script = """
<script>
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function pollForUpdates() {
while (true) {
const update = localStorage.getItem('sudoku_update');
if (update) {
localStorage.removeItem('sudoku_update');
fetch('/update_sudoku', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: update
});
}
await sleep(1); // Adjust polling interval as needed
}
}
pollForUpdates();
</script>
"""
st.components.v1.html(grid_html + poll_script, height=450)
# Handle updates posted to the backend
if 'update' in st.query_params:
update = st.query_params['update'][0]
try:
row, col, value = map(int, update.split(','))
st.session_state.sudoku_state[row, col] = value
except Exception as e:
st.error(f"Error parsing update: {e}")
# Display the current state of the Sudoku grid
st.write("Current State:")
st.write(st.session_state.sudoku_state)
if __name__ == "__main__":
main()