Created
December 8, 2019 17:11
-
-
Save malkia/a6a2a7f86d36517b2ebda917f990598c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/foundation.dart' | |
show debugDefaultTargetPlatformOverride; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/widgets.dart'; | |
import 'dart:math'; | |
var random = Random(0); | |
class GammonRules { | |
// The total number of points on the table. | |
static const numPoints = 24; | |
// The initial position tuples - [index, count] | |
static const initialPointTuples = const [ | |
[0, 2], | |
[11, 5], | |
[16, 3], | |
[18, 5] | |
]; | |
static List<int> initialPoints() { | |
var points = List<int>.filled(numPoints, 0); | |
for (var indexCount in initialPointTuples) { | |
var pointIndex = indexCount[0]; | |
var checkerCount = indexCount[1]; | |
// White checkers are positive. | |
points[pointIndex] = checkerCount; | |
// Black checkers are negative, placed mirrorwise. | |
points[numPoints - pointIndex - 1] = -checkerCount; | |
} | |
return points; | |
} | |
// Returns true if a checker can be moved, without hitting. | |
static bool canMove(int fromIndex, int toIndex, final List<int> points) { | |
if (points[fromIndex] == 0) return false; | |
if (points[toIndex] == 0) return true; | |
return points[fromIndex].sign == points[toIndex].sign; | |
} | |
// Returns true, if a piece can be hit, and taken out. | |
static bool canHit(int fromIndex, int toIndex, final List<int> points) { | |
if (points[fromIndex] == 0) return false; | |
if (points[fromIndex].sign == points[toIndex].sign) return false; | |
return points[toIndex].abs() == 1; | |
} | |
// Moves a checker without hitting. | |
static void doMove(int fromIndex, int toIndex, final List<int> points) { | |
assert(canMove(fromIndex, toIndex, points)); | |
int sideSign = points[fromIndex].sign; | |
points[fromIndex] = sideSign * (points[fromIndex].abs() - 1); | |
points[toIndex] = sideSign * (points[toIndex].abs() + 1); | |
} | |
// Hits a lone checker. | |
static void doHit( | |
int fromIndex, int toIndex, final List<int> points, List<int> hits) { | |
assert(canHit(fromIndex, toIndex, points)); | |
int sideSign = points[fromIndex].sign; | |
points[fromIndex] = sideSign * (points[fromIndex].abs() - 1); | |
points[toIndex] = sideSign; | |
} | |
static int sideIndex(int sideSign /* -1 or 1 */) { | |
assert(sideSign.abs() == 1); | |
return (sideSign + 1) >> 1; | |
} | |
// Returns true if the player has any hit checkers, false otherwise. | |
static bool hasHit(int sideSign, final List<int> hits) { | |
return hits[sideIndex(sideSign)] != 0; | |
} | |
// Adds a hit checker. | |
static void addHit(int sideSign, final List<int> hits) { | |
hits[sideIndex(sideSign)]++; | |
} | |
// Remove a hit checker. | |
static void removeHit(int sideSign, List<int> hits) { | |
hits[sideIndex(sideSign)]--; | |
} | |
} | |
class GammonState { | |
var points = GammonRules.initialPoints(); | |
var hits = [0, 0]; // Checkers hit per player | |
var dice = [0, 0]; // Dice roll | |
var sideSign = 1; // Either -1, or 1, or 0 if no game is playing | |
var turnCount = 0; | |
} | |
class TrianglePainter extends CustomPainter { | |
final GammonState _gammonState; | |
final bool _pointingDown; | |
final Color _color; | |
final int _point; | |
const TrianglePainter(this._gammonState, this._color, this._point) | |
: _pointingDown = _point < 12; | |
@override | |
void paint(Canvas canvas, Size size) { | |
var paint = Paint(); | |
paint.style = PaintingStyle.fill; | |
paint.color = _color; | |
var path = Path(); | |
if (_pointingDown) { | |
path.moveTo(0, 0); | |
path.lineTo(size.width / 2, size.height * 4 / 5); | |
path.lineTo(size.width, 0); | |
} else { | |
path.moveTo(0, size.height); | |
path.lineTo(size.width / 2, size.height / 5); | |
path.lineTo(size.width, size.height); | |
} | |
path.close(); | |
canvas.drawPath(path, paint); | |
paint.style = PaintingStyle.stroke; | |
paint.strokeWidth = 2; | |
paint.color = Colors.white30; | |
canvas.drawPath(path, paint); | |
var checker = _gammonState.points[_point]; | |
var count = checker.abs(); | |
Offset offset; | |
for (var i = 0; i < count; i++) { | |
if (i >= 5) break; | |
offset = Offset( | |
size.width / 2, | |
_pointingDown | |
? ((size.height / 7) * (i + 0.7)) | |
: (size.height - ((size.height / 7) * (i + 0.7)))); | |
var rect = Rect.fromCircle(center: offset, radius: size.width / 3); | |
paint.style = PaintingStyle.stroke; | |
paint.color = Colors.black87; | |
canvas.drawOval(rect, paint); | |
rect = rect.inflate(-1); | |
paint.color = checker > 0 ? Colors.white : Colors.red; | |
paint.style = PaintingStyle.fill; | |
canvas.drawOval(rect, paint); | |
} | |
if (count < 1) return; | |
offset = offset.translate( | |
count < 10 ? -size.width / 7 : -size.width / 3.5, -size.width / 3.5); | |
double fontSize = size.width / 2; | |
TextSpan span = TextSpan( | |
style: TextStyle( | |
color: Colors.black, | |
fontSize: fontSize, | |
), | |
text: '$count', | |
); | |
TextPainter tp = TextPainter( | |
text: span, | |
textAlign: TextAlign.left, | |
textDirection: TextDirection.ltr, | |
); | |
tp.layout(); | |
tp.paint(canvas, offset); | |
} | |
@override | |
bool shouldRepaint(CustomPainter oldDelegate) { | |
return false; | |
} | |
} | |
class BackgammonPoint extends StatefulWidget { | |
final GammonState _gammonState; | |
final Color _color; | |
final int _index; | |
const BackgammonPoint(this._gammonState, this._index) | |
: _color = _index % 2 == 0 ? Colors.grey : Colors.blueGrey; | |
@override | |
BackgammonPointState createState() => BackgammonPointState(); | |
} | |
class BackgammonPointState extends State<BackgammonPoint> { | |
bool _panning = false; | |
@override | |
Widget build(BuildContext context) => GestureDetector( | |
// onPanStart: (_) => setState(() => _panning = true), | |
// onPanUpdate: (_) => setState(() => _panning = true), | |
// onPanCancel: () => setState(() => _panning = false), | |
// onPanDown: (_) => setState(() => _panning = true), | |
// onPanEnd: (_) => setState(() => _panning = false), | |
//onTap: () => setState(() => _panning = true), | |
onTapCancel: () => setState(() => _panning = false), | |
onTapDown: (_) => setState(() => _panning = true), | |
onTapUp: (_) => setState(() => _panning = false), | |
// onPanDown: ((DragDownDetails d) => setState(() => _panning = true)), | |
// onPanStart: ((DragStartDetails d) => setState(() => _panning = true)), | |
// onPanEnd: ((DragEndDetails d) => setState(() => _panning = false)), | |
child: CustomPaint( | |
size: Size( | |
MediaQuery.of(context).size.width / 13, //64, | |
MediaQuery.of(context).size.height * 4 / 10, | |
), //256, | |
painter: TrianglePainter( | |
this.widget._gammonState, | |
_panning ? Colors.red : this.widget._color, | |
this.widget._index, | |
), | |
)); | |
} | |
class BackgammonHalfRow extends StatelessWidget { | |
final GammonState _gammonState; | |
final int _startIndex; | |
const BackgammonHalfRow(this._gammonState, this._startIndex); | |
@override | |
Widget build(BuildContext context) => Row(children: <Widget>[ | |
BackgammonPoint(_gammonState, _startIndex + 0), | |
BackgammonPoint(_gammonState, _startIndex + 1), | |
BackgammonPoint(_gammonState, _startIndex + 2), | |
BackgammonPoint(_gammonState, _startIndex + 3), | |
BackgammonPoint(_gammonState, _startIndex + 4), | |
BackgammonPoint(_gammonState, _startIndex + 5) | |
]); | |
} | |
class BackgammonFullRow extends StatelessWidget { | |
final GammonState _gammonState; | |
final int _startIndex; | |
const BackgammonFullRow(this._gammonState, this._startIndex); | |
@override | |
Widget build(BuildContext context) => Row(children: <Widget>[ | |
BackgammonHalfRow(_gammonState, _startIndex), | |
Container( | |
color: const Color(0xff808000), // Yellow | |
width: MediaQuery.of(context).size.width / 13, | |
), | |
BackgammonHalfRow(_gammonState, _startIndex + 6) | |
]); | |
} | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) => WidgetsApp( | |
title: 'Backgammon', | |
color: Colors.brown, | |
builder: (BuildContext context, Widget navigator) => MyWidget()); | |
} | |
class MyWidget extends StatefulWidget { | |
const MyWidget({Key key}) : super(key: key); | |
@override | |
MyWidgetState createState() => MyWidgetState(); | |
} | |
class MyWidgetState extends State<MyWidget> | |
with SingleTickerProviderStateMixin { | |
GammonState _gammonState; | |
AnimationController _animationController; | |
MyWidgetState() : _gammonState = GammonState(); | |
@override | |
initState() { | |
super.initState(); | |
throwDice(); | |
_animationController = AnimationController( | |
duration: const Duration(milliseconds: 10000), | |
animationBehavior: AnimationBehavior.normal, | |
vsync: this, | |
); | |
_animationController.addListener(() { | |
setState(() => randomMove()); | |
}); | |
//_animationController.forward(); | |
} | |
@override | |
void dispose() { | |
_animationController.dispose(); | |
super.dispose(); | |
} | |
void startAgain() { | |
_gammonState = GammonState(); | |
_animationController.repeat( | |
min: 0.0, | |
max: 1.0, | |
reverse: false, | |
period: const Duration(milliseconds: 10000)); | |
} | |
void throwDice() { | |
var gs = _gammonState; | |
gs.dice[0] = random.nextInt(6) + 1; | |
gs.dice[1] = random.nextInt(6) + 1; | |
} | |
void nextSide() { | |
var gs = _gammonState; | |
gs.sideSign = -gs.sideSign; | |
gs.turnCount++; | |
} | |
void randomMove() { | |
nextSide(); | |
throwDice(); | |
var gs = _gammonState; | |
var pc = gs.points; | |
for (var i = 0; i < pc.length - 1; i++) { | |
var x = i; | |
if (gs.sideSign == -1) x = GammonRules.numPoints - x - 1; | |
if (pc[x].sign != gs.sideSign) continue; | |
for (var d = 0; d < 2; d++) { | |
var j = i + gs.dice[d]; | |
if (j >= GammonRules.numPoints) continue; | |
var y = j; | |
if (gs.sideSign == -1) y = GammonRules.numPoints - y - 1; | |
if (GammonRules.canMove(x, y, pc)) { | |
GammonRules.doMove(x, y, pc); | |
return; | |
} | |
} | |
} | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Column(children: <Widget>[ | |
SizedBox( | |
height: MediaQuery.of(context).size.height / 10, | |
width: MediaQuery.of(context).size.width, | |
child: Text( | |
"back:gammon", | |
style: TextStyle(fontSize: MediaQuery.of(context).size.height / 15), | |
textAlign: TextAlign.center, | |
)), | |
Container( | |
color: const Color(0xff301000), // Yellow | |
child: BackgammonFullRow(_gammonState, 0), | |
), | |
Container( | |
color: const Color(0xff301000), // Yellow | |
child: BackgammonFullRow(_gammonState, 12), | |
), | |
SizedBox( | |
height: MediaQuery.of(context).size.height / 10, | |
width: MediaQuery.of(context).size.width, | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: <Widget>[ | |
Spacer(flex: 4), | |
RaisedButton( | |
child: Text( | |
"${_gammonState.dice[0]} ${_gammonState.dice[1]} (${_gammonState.turnCount})"), | |
onPressed: () => setState(() => randomMove()), | |
), | |
Spacer(flex: 1), | |
RaisedButton( | |
child: Text("RESET"), | |
onPressed: () => setState(() => startAgain()), | |
), | |
Spacer(flex: 4), | |
], | |
)) | |
]); | |
} | |
} | |
void main() { | |
// See https://github.com/flutter/flutter/wiki/Desktop-shells#target-platform-override | |
debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; | |
runApp(MyApp()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment