diff --git a/lib/clasic.dart b/lib/clasic.dart new file mode 100644 index 0000000..bc9ee39 --- /dev/null +++ b/lib/clasic.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'state.dart'; +import 'game.dart'; + +class ClassicGame extends StatefulWidget { + const ClassicGame({super.key}); + + @override + State createState() => _ClassicGameState(); +} + +class _ClassicGameState extends State { + TTCState turn = TTCState.x; + late TTCGame game; + + String get turnText => switch (turn) { + TTCState.empty => "", + TTCState.x => "X", + TTCState.o => "O", + }; + + void onTurnEnd() { + setState(() { + turn = switch (turn) { + TTCState.x => TTCState.o, + TTCState.o => TTCState.x, + _ => TTCState.x + }; + }); + } + + @override + void initState() { + super.initState(); + game = TTCGame( + turn: turn, + onClick: onTurnEnd, + ); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Spacer(flex: 5), + Center( + child: Text( + "$turnText's turn", + style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold), + )), + const Spacer(flex: 1), + TTCGame( + turn: turn, + onClick: onTurnEnd, + ), + const Spacer(flex: 5), + ], + ); + } +} diff --git a/lib/game.dart b/lib/game.dart new file mode 100644 index 0000000..75a9918 --- /dev/null +++ b/lib/game.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'state.dart'; +import 'util.dart'; + +/// Board of a single game of tic tac toe +class TTCGame extends StatefulWidget { + const TTCGame({ + super.key, + required this.turn, + this.data, + this.onClick, + }); + + final TTCState turn; + final List? data; + + /// hook into acction of tapping on square + final Function? onClick; + + @override + State createState() => _TTCGameState(); +} + +class _TTCGameState extends State { + late TTCState turn; + late List data; + + void setCellState(int index) { + switch (data[index]) { + case TTCState.empty: + setState(() { + data[index] = turn; + + turn = switch (turn) { + TTCState.x => TTCState.o, + TTCState.o => TTCState.x, + TTCState.empty => TTCState.empty, + }; + }); + widget.onClick?.call(); + notifyWin(); + break; + default: + ScaffoldMessenger.of(context) + ..clearSnackBars() + ..showSnackBar(const SnackBar( + content: Text("Invalid Choice: cell already has value"))); + } + } + + void notifyWin() { + TTCState state = Util.checkWin(data); + + if (state != TTCState.empty) { + ScaffoldMessenger.of(context) + ..clearSnackBars() + ..showSnackBar( + SnackBar(content: Text("${state.name.toUpperCase()} wins"))); + } + } + + Border _genCellBorder( + int index, { + BorderSide borderSide = const BorderSide(), + }) { + return Border( + top: index < 3 ? BorderSide.none : borderSide, + bottom: index > 5 ? BorderSide.none : borderSide, + left: [0, 3, 6].contains(index) ? BorderSide.none : borderSide, + right: [2, 5, 8].contains(index) ? BorderSide.none : borderSide, + ); + } + + Widget _genCell(int index, TTCState state) => Container( + decoration: BoxDecoration(border: _genCellBorder(index)), + child: TTCCell( + state: state, + stateSetCallback: () { + setCellState(index); + }, + )); + + @override + void initState() { + super.initState(); + turn = widget.turn; + data = widget.data ?? + [ + TTCState.empty, + TTCState.empty, + TTCState.empty, + TTCState.empty, + TTCState.empty, + TTCState.empty, + TTCState.empty, + TTCState.empty, + TTCState.empty, + ]; + } + + @override + Widget build(BuildContext context) { + List cells = []; + for (final (index, state) in data.indexed) { + cells.add(_genCell(index, state)); + } + return GridView.count( + crossAxisCount: 3, + shrinkWrap: true, + children: cells, + ); + } +} + +class TTCCell extends StatelessWidget { + const TTCCell({ + super.key, + required this.state, + this.stateSetCallback, + this.textStyle, + }); + + final TTCState state; + final VoidCallback? stateSetCallback; + final TextStyle? textStyle; + + String get text => switch (state) { + TTCState.empty => "", + TTCState.x => "X", + TTCState.o => "O", + }; + + @override + Widget build(BuildContext context) => InkWell( + onTap: stateSetCallback, + child: Center( + child: Text(text, style: textStyle), + )); +} diff --git a/lib/main.dart b/lib/main.dart index 7839b04..c297a40 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'clasic.dart'; void main() { runApp(const MyApp()); @@ -30,238 +31,26 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - TTCState turn = TTCState.x; - late TTCGame game; - - String get turnText => switch (turn) { - TTCState.empty => "", - TTCState.x => "X", - TTCState.o => "O", - }; - - void onTurnEnd() { - setState(() { - turn = switch (turn) { - TTCState.x => TTCState.o, - TTCState.o => TTCState.x, - _ => TTCState.x - }; - }); - } - - @override - void initState() { - super.initState(); - game = TTCGame( - turn: turn, - onClick: onTurnEnd, - ); - } - @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(widget.title), - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - ), - body: Padding( + drawer: Drawer( + child: ListView( + children: [ + DrawerHeader( + child: Text("Game Types"), + ), + ListTile(title: Text("Clasic")), + ListTile(title: Text("Super")), + ], + )), + appBar: AppBar( + title: Text(widget.title), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + ), + body: Padding( padding: const EdgeInsets.all(10), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Spacer(flex: 5), - Center( - child: Text( - "$turnText's turn", - style: - const TextStyle(fontSize: 30, fontWeight: FontWeight.bold), - )), - const Spacer(flex: 1), - TTCGame( - turn: turn, - onClick: onTurnEnd, - ), - const Spacer(flex: 5), - ], - )), - ); + child: ClassicGame(), + )); } } - -enum TTCState { empty, x, o } - -/// Board of a single game of tic tac toe -class TTCGame extends StatefulWidget { - const TTCGame({ - super.key, - required this.turn, - this.data, - this.onClick, - }); - - final TTCState turn; - final List? data; - - /// hook into acction of tapping on square - final Function? onClick; - - @override - State createState() => _TTCGameState(); -} - -class _TTCGameState extends State { - late TTCState turn; - late List data; - - void setCellState(int index) { - switch (data[index]) { - case TTCState.empty: - setState(() { - data[index] = turn; - - turn = switch (turn) { - TTCState.x => TTCState.o, - TTCState.o => TTCState.x, - TTCState.empty => TTCState.empty, - }; - }); - widget.onClick?.call(); - notifyWin(); - break; - default: - ScaffoldMessenger.of(context) - ..clearSnackBars() - ..showSnackBar(const SnackBar( - content: Text("Invalid Choice: cell already has value"))); - } - } - - Iterable getRow(int index) => data.getRange(index, index + 3); - Iterable getCol(int index) => [ - data[index], - data[index + 3], - data[index + 3 * 2], - ]; - Iterable getCross(int index) { - if (index == 0) { - return [data[0], data[4], data[8]]; - } - return [data[3], data[4], data[6]]; - } - - TTCState _checkRow(Iterable row) => row.reduce((value, element) => - (element == TTCState.empty || value == TTCState.empty || element != value) - ? TTCState.empty - : value); - - TTCState _checkBoard(Iterable rowValues) => - rowValues.reduce((value, element) { - if (value != TTCState.empty) { - return value; - } - if (element != TTCState.empty) { - return element; - } - - return TTCState.empty; - }); - - TTCState _checkWin() { - Iterable rows = Iterable.generate(3, (i) => _checkRow(getRow(i))); - Iterable cols = Iterable.generate(3, (i) => _checkRow(getCol(i))); - Iterable crosses = - Iterable.generate(2, (i) => _checkRow(getCross(i))); - - return _checkBoard([...rows, ...cols, ...crosses]); - } - - void notifyWin() { - TTCState state = _checkWin(); - - if (state != TTCState.empty) { - ScaffoldMessenger.of(context) - ..clearSnackBars() - ..showSnackBar( - SnackBar(content: Text("${state.name.toUpperCase()} wins"))); - } - } - - Border _genCellBorder( - int index, { - BorderSide borderSide = const BorderSide(), - }) { - return Border( - top: index < 3 ? BorderSide.none : borderSide, - bottom: index > 5 ? BorderSide.none : borderSide, - left: [0, 3, 6].contains(index) ? BorderSide.none : borderSide, - right: [2, 5, 8].contains(index) ? BorderSide.none : borderSide, - ); - } - - Widget _genCell(int index, TTCState state) => Container( - decoration: BoxDecoration(border: _genCellBorder(index)), - child: TTCCell( - state: state, - stateSetCallback: () { - setCellState(index); - }, - )); - - @override - void initState() { - super.initState(); - turn = widget.turn; - data = widget.data ?? - [ - TTCState.empty, - TTCState.empty, - TTCState.empty, - TTCState.empty, - TTCState.empty, - TTCState.empty, - TTCState.empty, - TTCState.empty, - TTCState.empty, - ]; - } - - @override - Widget build(BuildContext context) { - List cells = []; - for (final (index, state) in data.indexed) { - cells.add(_genCell(index, state)); - } - return GridView.count( - crossAxisCount: 3, - shrinkWrap: true, - children: cells, - ); - } -} - -class TTCCell extends StatelessWidget { - const TTCCell({ - super.key, - required this.state, - this.stateSetCallback, - this.textStyle, - }); - - final TTCState state; - final VoidCallback? stateSetCallback; - final TextStyle? textStyle; - - String get text => switch (state) { - TTCState.empty => "", - TTCState.x => "X", - TTCState.o => "O", - }; - - @override - Widget build(BuildContext context) => InkWell( - onTap: stateSetCallback, - child: Center( - child: Text(text, style: textStyle), - )); -} diff --git a/lib/state.dart b/lib/state.dart new file mode 100644 index 0000000..8cd2f36 --- /dev/null +++ b/lib/state.dart @@ -0,0 +1 @@ +enum TTCState { empty, x, o } diff --git a/lib/util.dart b/lib/util.dart new file mode 100644 index 0000000..62ed3be --- /dev/null +++ b/lib/util.dart @@ -0,0 +1,47 @@ +import 'state.dart'; + +class Util { + static Iterable getRow(int index, List data) => + data.getRange(index, index + 3); + static Iterable getCol(int index, List data) => [ + data[index], + data[index + 3], + data[index + 3 * 2], + ]; + static Iterable getCross(int index, List data) { + if (index == 0) { + return [data[0], data[4], data[8]]; + } + return [data[3], data[4], data[6]]; + } + + static TTCState checkRow(Iterable row) => + row.reduce((value, element) => (element == TTCState.empty || + value == TTCState.empty || + element != value) + ? TTCState.empty + : value); + + static TTCState checkBoard(Iterable rowValues) => + rowValues.reduce((value, element) { + if (value != TTCState.empty) { + return value; + } + if (element != TTCState.empty) { + return element; + } + + return TTCState.empty; + }); + + static TTCState checkWin(List data) { + Iterable rows = + Iterable.generate(3, (i) => checkRow(getRow(i, data))); + Iterable cols = + Iterable.generate(3, (i) => checkRow(getCol(i, data))); + Iterable crosses = + Iterable.generate(2, (i) => checkRow(getCross(i, data))); + + return checkBoard([...rows, ...cols, ...crosses]); + } +}