Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/2 display sudoku board #18

Open
wants to merge 5 commits into
base: develop/1-sudoku-game-screen-ui
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 2 additions & 16 deletions frontend/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
import 'package:flutter/material.dart';
import 'package:frontend/page/sudoku_game.dart';

void main() {
runApp(const MainApp());
}

class MainApp extends StatelessWidget {
const MainApp({super.key});

@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: Text('Hello World!'),
),
),
);
}
runApp(SudokuApp());
}
204 changes: 204 additions & 0 deletions frontend/lib/page/sudoku_game.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class SudokuApp extends StatefulWidget {
@override
_SudokuAppState createState() => _SudokuAppState();
}


class _SudokuAppState extends State<SudokuApp> {
bool isNoteMode = false;

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Sudoku'),
actions: [
IconButton(
icon: Icon(isNoteMode ? Icons.note : Icons.edit),
onPressed: toggleMode,
),
],
),
body: Shortcuts(
shortcuts: {
LogicalKeySet(LogicalKeyboardKey.space): ToggleModeIntent(),
},
child: Actions(
actions: {
ToggleModeIntent: CallbackAction<ToggleModeIntent>(
onInvoke: (intent) => toggleMode(),
),
},
child: Focus(
autofocus: true,
child: SudokuGrid(isNoteMode: isNoteMode),
),
),
),
),
);
}

void toggleMode() {
setState(() {
isNoteMode = !isNoteMode;
});
}
}

class ToggleModeIntent extends Intent {}

class SudokuGrid extends StatefulWidget {
final bool isNoteMode;

SudokuGrid({required this.isNoteMode});

@override
_SudokuGridState createState() => _SudokuGridState();
}

class _SudokuGridState extends State<SudokuGrid> {
final List<List<int>> sudokuBoard = List.generate(9, (_) => List.generate(9, (_) => 0));
final List<List<Set<int>>> notes = List.generate(9, (_) => List.generate(9, (_) => <int>{}));

@override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
double screenHeight = MediaQuery.of(context).size.height;
double availableHeight = screenHeight - kToolbarHeight - MediaQuery.of(context).padding.top - 16.0;
double gridSize = screenWidth < availableHeight ? screenWidth : availableHeight;

return Center(
child: Container(
width: gridSize,
height: gridSize,
padding: const EdgeInsets.all(8.0),
child: GridView.builder(
itemCount: 81,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 9,
childAspectRatio: 1.0,
),
itemBuilder: (context, index) {
int row = index ~/ 9;
int col = index % 9;
return SudokuCell(
value: sudokuBoard[row][col],
notes: notes[row][col],
isNoteMode: widget.isNoteMode,
onChanged: (newValue) {
setState(() {
if (widget.isNoteMode) {
// Add or remove notes based on the user's input
for (int note in newValue) {
if (notes[row][col].contains(note)) {
notes[row][col].remove(note);
} else {
notes[row][col].add(note);
}
}
} else {
if (newValue == [0]) {
// Clear the cell and show the previous notes
sudokuBoard[row][col] = 0;
} else {
// Set the answer and clear the notes
sudokuBoard[row][col] = newValue.first;
// notes[row][col].clear();
}
}
});
},
onCleared: () {
setState(() {
// When the cell is cleared, make sure the notes become visible again
sudokuBoard[row][col] = 0;
});
},
);
},
),
),
);
}
}

class SudokuCell extends StatelessWidget {
final int value;
final Set<int> notes;
final bool isNoteMode;
final Function(List<int>) onChanged;
final Function onCleared;

SudokuCell({required this.value, required this.notes, required this.isNoteMode, required this.onChanged, required this.onCleared});

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(1.0),
child: GestureDetector(
onTap: () async {
String? input = await showDialog<String>(
context: context,
builder: (context) => NumberInputDialog(isNoteMode: isNoteMode),
);
if (input != null) {
input = input.replaceAll(' ', ''); // Remove spaces
if (input.isEmpty) {
onCleared(); // Clear the cell and restore notes
} else {
List<int> newValues = input.split('').map(int.parse).where((n) => n >= 1 && n <= 9).toList();
if (newValues.isNotEmpty) {
onChanged(newValues);
}
}
}
},
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
),
child: Center(
child: value == 0 && notes.isNotEmpty
? Text(
notes.map((e) => e.toString()).join(', '),
textAlign: TextAlign.center,
style: TextStyle(fontSize: 10, color: Colors.grey),
)
: Text(
value == 0 ? '' : value.toString(),
textAlign: TextAlign.center,
style: TextStyle(fontSize: 24),
),
),
),
),
);
}
}

class NumberInputDialog extends StatelessWidget {
final bool isNoteMode;

NumberInputDialog({required this.isNoteMode});

@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(isNoteMode ? 'Enter Numbers for Notes' : 'Enter Number'),
content: TextField(
keyboardType: TextInputType.text,
decoration: InputDecoration(hintText: isNoteMode ? 'Enter numbers (1-9) separated by spaces or without spaces' : 'Enter a number (1-9) or leave empty to clear'),
onSubmitted: (value) {
if (Navigator.of(context).maybePop(value) == true) {
Navigator.of(context).pop(value);
}
},
),
);
}
}
51 changes: 47 additions & 4 deletions frontend/widgetbook/lib/main.directories.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,61 @@
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:widgetbook/widgetbook.dart' as _i1;
import 'package:widgetbook_workspace/examples/cool_button.dart' as _i2;
import 'package:widgetbook_workspace/sudoku_game/sudoku_game.dart' as _i3;

final directories = <_i1.WidgetbookNode>[
_i1.WidgetbookFolder(
name: 'examples',
children: [
_i1.WidgetbookLeafComponent(
name: 'CoolButton',
name: 'NumberInputDialog(Answer mode)',
useCase: _i1.WidgetbookUseCase(
name: 'Deafult',
builder: _i2.buildCoolButtonUseCase,
name: 'NumberInputDialog(Answer mode)',
builder: _i3.buildNumberInputDialogAnswerUseCase,
),
)
),
_i1.WidgetbookLeafComponent(
name: 'NumberInputDialog(Note mode)',
useCase: _i1.WidgetbookUseCase(
name: 'NumberInputDialog(Note mode)',
builder: _i3.buildNumberInputDialogNoteUseCase,
),
),
_i1.WidgetbookLeafComponent(
name: 'SudokuCell(Answer mode)',
useCase: _i1.WidgetbookUseCase(
name: 'SudokuCell(Answer mode)',
builder: _i3.buildSudokuCellAnswerUseCase,
),
),
_i1.WidgetbookLeafComponent(
name: 'SudokuCell(Note mode)',
useCase: _i1.WidgetbookUseCase(
name: 'SudokuCell(Note mode)',
builder: _i3.buildSudokuCellNoteUseCase,
),
),
_i1.WidgetbookLeafComponent(
name: 'SudokuGrid(Answer mode)',
useCase: _i1.WidgetbookUseCase(
name: 'SudokuGrid(Answer mode)',
builder: _i3.buildSudokuGridAnswerUseCase,
),
),
_i1.WidgetbookLeafComponent(
name: 'SudokuGrid(Note mode)',
useCase: _i1.WidgetbookUseCase(
name: 'SudokuGrid(Note mode)',
builder: _i3.buildSudokuGridNoteUseCase,
),
),
_i1.WidgetbookLeafComponent(
name: 'SudokuApp',
useCase: _i1.WidgetbookUseCase(
name: 'SudokuApp',
builder: _i3.buildSudokuAppUseCase,
),
),
],
)
];
57 changes: 57 additions & 0 deletions frontend/widgetbook/lib/sudoku_game/sudoku_game.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:frontend/page/sudoku_game.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;

@widgetbook.UseCase(name: 'NumberInputDialog(Answer mode)', type: NumberInputDialog)
Widget buildNumberInputDialogAnswerUseCase(BuildContext context) {
return NumberInputDialog(isNoteMode: false);
}

@widgetbook.UseCase(name: 'NumberInputDialog(Note mode)', type: NumberInputDialog)
Widget buildNumberInputDialogNoteUseCase(BuildContext context) {
return NumberInputDialog(isNoteMode: true);
}

@widgetbook.UseCase(name: 'SudokuCell(Answer mode)', type: SudokuCell)
Widget buildSudokuCellAnswerUseCase(BuildContext context) {
int value = 0;
Set<int> notes = <int>{};
bool isNoteMode = false;
return SudokuCell(
value: value,
notes: notes,
isNoteMode: isNoteMode,
onChanged: (_) { return List.generate(9, (_) => <int>{});},
onCleared: (_){},
);
}


@widgetbook.UseCase(name: 'SudokuCell(Note mode)', type: SudokuCell)
Widget buildSudokuCellNoteUseCase(BuildContext context) {
int value = 0;
Set<int> notes = <int>{};
bool isNoteMode = true;
return SudokuCell(
value: value,
notes: notes,
isNoteMode: isNoteMode,
onChanged: (_) { return List.generate(9, (_) => <int>{});},
onCleared: (_){},
);
}

@widgetbook.UseCase(name: 'SudokuGrid(Answer mode)', type: SudokuGrid)
Widget buildSudokuGridAnswerUseCase(BuildContext context) {
return SudokuGrid(isNoteMode: false);
}

@widgetbook.UseCase(name: 'SudokuGrid(Note mode)', type: SudokuGrid)
Widget buildSudokuGridNoteUseCase(BuildContext context) {
return SudokuGrid(isNoteMode: true);
}

@widgetbook.UseCase(name: 'SudokuApp', type: SudokuApp)
Widget buildSudokuAppUseCase(BuildContext context) {
return SudokuApp();
}