Skip to content

Instantly share code, notes, and snippets.

@malkia
Created December 8, 2019 17:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save malkia/a6a2a7f86d36517b2ebda917f990598c to your computer and use it in GitHub Desktop.
Save malkia/a6a2a7f86d36517b2ebda917f990598c to your computer and use it in GitHub Desktop.
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