Pen Settings

JavaScript

Babel includes JSX processing.

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

Flutter

              
                import 'package:flutter/material.dart';
import 'dart:math';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter fiar',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
      onGenerateRoute: (settings) {
        final args = settings.arguments as Map<String, dynamic>;
        if (settings.name == '/match') {
          return MaterialPageRoute(
            builder: (context) => MatchPage(
              mode: args['mode'],
              cpu: args['cpu'],
              cpu2: args['cpu2'],
            ),
          );
        } else if (settings.name == '/cpu-level') {
          return MaterialPageRoute(
            builder: (context) => CpuLevelPage(),
          );
        }

        return null;
      },
    );
  }
}

class CpuLevelPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        elevation: 0,
      ),
      backgroundColor: Colors.blue,
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisSize: MainAxisSize.max,
          children: <Widget>[
            FlatButton(
              color: Colors.yellow,
              child: Text(
                'DUMB',
                style: Theme.of(context)
                    .textTheme
                    .headline3
                    .copyWith(color: Colors.black),
              ),
              onPressed: () {
                Navigator.pushNamed(
                  context,
                  '/match',
                  arguments: {
                    'mode': Mode.PVC,
                    'cpu':
                        DumbCpu(Random().nextBool() ? Color.RED : Color.YELLOW),
                  },
                );
              },
            ),
            FlatButton(
              color: Colors.red,
              child: Text(
                'HARD',
                style: Theme.of(context)
                    .textTheme
                    .headline3
                    .copyWith(color: Colors.white),
              ),
              onPressed: () {
                Navigator.pushNamed(
                  context,
                  '/match',
                  arguments: {
                    'mode': Mode.PVC,
                    'cpu': HarderCpu(
                        Random().nextBool() ? Color.RED : Color.YELLOW),
                  },
                );
              },
            ),
            FlatButton(
              color: Colors.deepPurpleAccent,
              child: Text(
                'HARDEST',
                style: Theme.of(context)
                    .textTheme
                    .headline3
                    .copyWith(color: Colors.white),
              ),
              onPressed: () {
                Navigator.pushNamed(
                  context,
                  '/match',
                  arguments: {
                    'mode': Mode.PVC,
                    'cpu': HardestCpu(
                        Random().nextBool() ? Color.RED : Color.YELLOW),
                  },
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.blue,
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisSize: MainAxisSize.max,
          children: <Widget>[
            FlatButton(
              color: Colors.green,
              child: Text(
                'VS PLAYER',
                style: Theme.of(context)
                    .textTheme
                    .headline3
                    .copyWith(color: Colors.white),
              ),
              onPressed: () {
                Navigator.pushNamed(
                  context,
                  '/match',
                  arguments: {
                    'mode': Mode.PVP,
                  },
                );
              },
            ),
            FlatButton(
              color: Colors.black,
              child: Text(
                'VS CPU',
                style: Theme.of(context)
                    .textTheme
                    .headline3
                    .copyWith(color: Colors.white),
              ),
              onPressed: () {
                Navigator.pushNamed(
                  context,
                  '/cpu-level',
                  arguments: {
                    'mode': Mode.PVC,
                  },
                );
              },
            ),
            FlatButton(
              color: Colors.white,
              child: Text(
                'DEMO',
                style: Theme.of(context)
                    .textTheme
                    .headline3
                    .copyWith(color: Colors.black),
              ),
              onPressed: () {
                final harderCpu =
                    HarderCpu(Random().nextBool() ? Color.RED : Color.YELLOW);
                Navigator.pushNamed(
                  context,
                  '/match',
                  arguments: {
                    'mode': Mode.DEMO,
                    'cpu': harderCpu,
                    'cpu2': HardestCpu(harderCpu.otherPlayer),
                  },
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

enum Color {
  YELLOW,
  RED,
}

enum Mode {
  PVP,
  PVC,
  DEMO,
}

class MatchPage extends StatefulWidget {
  final Mode mode;
  final Cpu cpu;
  final Cpu cpu2;

  const MatchPage({
    Key key,
    this.mode,
    this.cpu,
    this.cpu2,
  }) : super(key: key);

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

class _MatchPageState extends State<MatchPage> with TickerProviderStateMixin {
  final board = Board();
  Color turn;
  Color winner;

  List<List<Animation<double>>> translations = List.generate(
    7,
    (i) => List.generate(
      7,
      (i) => null,
    ),
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        elevation: 0,
      ),
      backgroundColor: Colors.blue,
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Flex(
          direction: Axis.vertical,
          mainAxisSize: MainAxisSize.max,
          children: <Widget>[
            Flexible(
              flex: 2,
              child: Container(
                constraints: BoxConstraints.loose(
                  Size(
                    500,
                    532,
                  ),
                ),
                child: Padding(
                  padding: const EdgeInsets.only(top: 32.0),
                  child: Stack(
                    overflow: Overflow.clip,
                    fit: StackFit.loose,
                    children: <Widget>[
                      Positioned.fill(
                        child: Container(
                          color: Colors.white,
                        ),
                      ),
                      buildPieces(),
                      buildBoard(),
                    ],
                  ),
                ),
              ),
            ),
            Flexible(
              flex: 1,
              child: Padding(
                padding: const EdgeInsets.all(32.0),
                child: winner != null
                    ? Text(
                        '${winner == Color.RED ? 'RED' : 'YELLOW'} WINS',
                        textAlign: TextAlign.center,
                        style: Theme.of(context)
                            .textTheme
                            .headline2
                            .copyWith(color: Colors.white),
                      )
                    : Column(
                        children: <Widget>[
                          Text(
                            '${turn == Color.RED ? 'RED' : 'YELLOW'} SPEAKS',
                            textAlign: TextAlign.center,
                            style: Theme.of(context)
                                .textTheme
                                .bodyText1
                                .copyWith(color: Colors.white),
                          ),
                          Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: GameChip(color: turn),
                          ),
                          _buildPlayerName(context),
                        ],
                      ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Text _buildPlayerName(BuildContext context) {
    String name;

    if (widget.mode == Mode.PVC) {
      if (turn == widget.cpu.color) {
        name = 'CPU - ${widget.cpu.toString()}';
      } else {
        name = 'USER';
      }
    } else if (widget.mode == Mode.PVP) {
      if (turn == Color.RED) {
        name = 'PLAYER1';
      } else {
        name = 'PLAYER2';
      }
    } else {
      if (turn == widget.cpu.color) {
        name = 'CPU1 - ${widget.cpu.toString()}';
      } else {
        name = 'CPU2 - ${widget.cpu2.toString()}';
      }
    }
    return Text(
      name,
      textAlign: TextAlign.center,
      style:
          Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.white),
    );
  }

  @override
  void initState() {
    super.initState();
    turn = widget.cpu?.otherPlayer ??
        (Random().nextBool() ? Color.RED : Color.YELLOW);
    if (widget.mode == Mode.PVC && turn == widget.cpu.color) {
      cpuMove(widget.cpu);
    } else if (widget.mode == Mode.DEMO) {
      if (turn == widget.cpu.color) {
        cpuMove(widget.cpu);
      } else {
        cpuMove(widget.cpu2);
      }
    }
  }

  GridView buildPieces() {
    return GridView.custom(
      padding: const EdgeInsets.all(0),
      shrinkWrap: true,
      physics: NeverScrollableScrollPhysics(),
      gridDelegate:
          SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 7),
      childrenDelegate: SliverChildBuilderDelegate(
        (context, i) {
          final col = i % 7;
          final row = i ~/ 7;

          if (board.getBox(Coordinate(col, row)) == null) {
            return SizedBox();
          }

          return GameChip(
            translation: translations[col][row],
            color: board.getBox(Coordinate(col, row)),
          );
        },
        childCount: 49,
      ),
    );
  }

  GridView buildBoard() {
    return GridView.custom(
      padding: const EdgeInsets.all(0),
      physics: NeverScrollableScrollPhysics(),
      gridDelegate:
          SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 7),
      shrinkWrap: true,
      childrenDelegate: SliverChildBuilderDelegate(
        (context, i) {
          final col = i % 7;

          return GestureDetector(
            onTap: () {
              if (winner == null) {
                userMove(col);
              }
            },
            child: CustomPaint(
              size: Size(50, 50),
              willChange: false,
              painter: HolePainter(),
            ),
          );
        },
        childCount: 49,
      ),
    );
  }

  void userMove(int col) {
    putChip(col);
    if (winner == null && widget.mode == Mode.PVC) {
      cpuMove(widget.cpu);
    }
  }

  void cpuMove(Cpu cpu) async {
    int col = await cpu.chooseCol(board);
    putChip(col);

    if (winner == null && widget.mode == Mode.DEMO) {
      if (turn == widget.cpu.color) {
        cpuMove(widget.cpu);
      } else {
        cpuMove(widget.cpu2);
      }
    }
  }

  void putChip(int col) {
    final target = board.getColumnTarget(col);
    final player = turn;

    if (target == -1) {
      return;
    }

    final controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 1),
    )..addListener(() {
        if (mounted) {
          setState(() {});
        }
      });

    if (mounted) {
      setState(() {
        board.setBox(Coordinate(col, target), turn);
        turn = turn == Color.RED ? Color.YELLOW : Color.RED;
      });
    }

    translations[col][target] = Tween(
      begin: 0.0,
      end: 1.0,
    ).animate(CurvedAnimation(
      curve: Curves.bounceOut,
      parent: controller,
    ))
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          controller.dispose();
        }
      });

    controller.forward().orCancel;

    if (board.checkWinner(Coordinate(col, target), player)) {
      showWinnerDialog(context, player);
    }
  }

  void showWinnerDialog(BuildContext context, Color player) {
    setState(() {
      winner = player;
    });

    Future.delayed(
      Duration(seconds: 5),
      () => mounted ? Navigator.popUntil(context, (r) => r.isFirst) : null,
    );
  }

  void resetBoard() {
    setState(() {
      winner = null;
      board.reset();
    });
  }
}

class Coordinate {
  final int row, col;

  Coordinate(
    this.col,
    this.row,
  );

  Coordinate copyWith({
    int col,
    int row,
  }) =>
      Coordinate(
        col ?? this.col,
        row ?? this.row,
      );
}

abstract class Cpu {
  final Color color;
  final Random _random = Random(DateTime.now().millisecond);

  Cpu(this.color);

  Color get otherPlayer => color == Color.RED ? Color.YELLOW : Color.RED;

  Future<int> chooseCol(Board board);
}

class DumbCpu extends Cpu {
  DumbCpu(Color player) : super(player);

  Color get otherPlayer => color == Color.RED ? Color.YELLOW : Color.RED;

  @override
  Future<int> chooseCol(Board board) async {
    await Future.delayed(Duration(seconds: _random.nextInt(2)));
    int col = _random.nextInt(7);

    return col;
  }

  @override
  String toString() => 'DUMB CPU';
}

class HarderCpu extends Cpu {
  HarderCpu(Color player) : super(player);

  @override
  Future<int> chooseCol(Board board) async {
    final List<double> scores = List.filled(7, 0);

    await Future.delayed(Duration(seconds: 1 + _random.nextInt(2)));
    return _compute(board, 0, 1, scores);
  }

  int _compute(Board board, int step, int deepness, List<double> scores) {
    for (var i = 0; i < 7; ++i) {
      final boardCopy = board.clone();

      final target = boardCopy.getColumnTarget(i);
      if (target == -1) {
        scores[i] = null;
        continue;
      }

      final coordinate = Coordinate(i, target);

      boardCopy.setBox(coordinate, color);
      if (boardCopy.checkWinner(coordinate, color)) {
        scores[i] += deepness / (step + 1);
        continue;
      }

      for (var j = 0; j < 7; ++j) {
        final target = boardCopy.getColumnTarget(j);
        if (target == -1) {
          continue;
        }

        final coordinate = Coordinate(j, target);

        boardCopy.setBox(coordinate, otherPlayer);
        if (boardCopy.checkWinner(coordinate, otherPlayer)) {
          scores[i] -= deepness / (step + 1);
          continue;
        }

        if (step + 1 < deepness) {
          _compute(board, step + 1, deepness, scores);
        }
      }
    }

    return _getBestScoreIndex(scores);
  }

  int _getBestScoreIndex(List<double> scores) {
    int bestScoreIndex = scores.indexWhere((s) => s != null);
    scores.asMap().forEach((index, score) {
      if (score != null &&
          (score > scores[bestScoreIndex] ||
              (score == scores[bestScoreIndex] && _random.nextBool()))) {
        bestScoreIndex = index;
      }
    });
    return bestScoreIndex;
  }

  @override
  String toString() => 'HARDER CPU';
}

class HardestCpu extends HarderCpu {
  HardestCpu(Color player) : super(player);

  @override
  Future<int> chooseCol(Board board) async {
    final List<double> scores = List.filled(7, 0);

    await Future.delayed(Duration(seconds: 2 + _random.nextInt(2)));
    return _compute(board, 0, 4, scores);
  }

  @override
  String toString() => 'HARDEST CPU';
}

class Board {
  List<List<Color>> _boxes = List.generate(
    7,
    (i) => List.generate(
      7,
      (i) => null,
    ),
  );

  Board();

  Board.from(List<List<Color>> boxes) {
    _boxes = boxes;
  }

  Color getBox(Coordinate coordinate) => _boxes[coordinate.col][coordinate.row];

  int getColumnTarget(int col) => _boxes[col].lastIndexOf(null);

  void setBox(Coordinate coordinate, Color player) =>
      _boxes[coordinate.col][coordinate.row] = player;

  void reset() {
    _boxes.forEach((r) => r.forEach((p) => p = null));
  }

  bool checkWinner(Coordinate coordinate, Color player) {
    return checkHorizontally(coordinate, player) ||
        checkVertically(coordinate, player) ||
        checkDiagonally(coordinate, player);
  }

  bool checkHorizontally(Coordinate coordinate, Color player) {
    var r = 0;
    for (;
        coordinate.col + r < 7 &&
            r < 4 &&
            getBox(coordinate.copyWith(col: coordinate.col + r)) == player;
        ++r) {}
    if (r >= 4) {
      return true;
    }

    var l = 0;
    for (;
        coordinate.col - l >= 0 &&
            l < 4 &&
            getBox(coordinate.copyWith(col: coordinate.col - l)) == player;
        ++l) {}
    if (l >= 4 || l + r >= 5) {
      return true;
    }

    return false;
  }

  bool checkDiagonally(Coordinate coordinate, Color player) {
    var ur = 0;
    for (;
        coordinate.col + ur < 7 &&
            coordinate.row + ur < 7 &&
            ur < 4 &&
            getBox(coordinate.copyWith(
                  col: coordinate.col + ur,
                  row: coordinate.row + ur,
                )) ==
                player;
        ++ur) {}
    if (ur >= 4) {
      return true;
    }
    var dl = 0;
    for (;
        coordinate.col - dl >= 0 &&
            coordinate.row - dl >= 0 &&
            dl < 4 &&
            getBox(coordinate.copyWith(
                  col: coordinate.col - dl,
                  row: coordinate.row - dl,
                )) ==
                player;
        ++dl) {}
    if (dl >= 4 || dl + ur >= 5) {
      return true;
    }

    var dr = 0;
    for (;
        coordinate.col + dr < 7 &&
            coordinate.row - dr >= 0 &&
            dr < 4 &&
            getBox(coordinate.copyWith(
                  col: coordinate.col + dr,
                  row: coordinate.row - dr,
                )) ==
                player;
        ++dr) {}
    if (dr >= 4) {
      return true;
    }

    var ul = 0;
    for (;
        coordinate.col - ul >= 0 &&
            coordinate.row + ul < 7 &&
            ul < 4 &&
            getBox(coordinate.copyWith(
                  col: coordinate.col - ul,
                  row: coordinate.row + ul,
                )) ==
                player;
        ++ul) {}
    if (ul >= 4 || dr + ul >= 5) {
      return true;
    }
    return false;
  }

  bool checkVertically(Coordinate coordinate, Color player) {
    var u = 0;
    for (;
        coordinate.row + u < 7 &&
            u < 4 &&
            getBox(coordinate.copyWith(
                  row: coordinate.row + u,
                )) ==
                player;
        ++u) {}
    if (u >= 4) {
      return true;
    }
    var d = 0;
    for (;
        coordinate.row - d >= 0 &&
            d < 4 &&
            getBox(coordinate.copyWith(
                  row: coordinate.row - d,
                )) ==
                player;
        ++d) {}
    if (d >= 4 || d + u >= 5) {
      return true;
    }

    return false;
  }

  Board clone() {
    return Board.from(_boxes.map((c) => c.map((b) => b).toList()).toList());
  }
}

class HolePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint();
    paint.color = Colors.blue;
    paint.style = PaintingStyle.fill;

    final center = Offset(size.height / 2, size.width / 2);

    final circleBounds = Rect.fromCircle(center: center, radius: 20);

    final topPath = Path()
      ..moveTo(-1, -1)
      ..lineTo(-1, (size.height / 2) + 1)
      ..arcTo(circleBounds, -pi, pi, false)
      ..lineTo(size.width + 1, (size.height / 2) + 1)
      ..lineTo(size.width + 1, -1)
      ..close();
    final bottomPath = Path()
      ..moveTo(-1, size.height)
      ..lineTo(-1, (size.height / 2) - 1)
      ..arcTo(circleBounds, pi, -pi, false)
      ..lineTo(size.width + 1, (size.height / 2) - 1)
      ..lineTo(size.width + 1, size.height + 1)
      ..close();

    canvas.drawPath(topPath, paint);
    canvas.drawPath(bottomPath, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return false;
  }
}

class GameChip extends StatelessWidget {
  const GameChip({
    Key key,
    this.translation,
    @required this.color,
  }) : super(key: key);

  final Animation<double> translation;
  final Color color;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        transform: Matrix4.translationValues(
          0,
          ((translation?.value ?? 1) * 400) - 400,
          0,
        ),
        height: 40,
        width: 40,
        child: Material(
          shape: CircleBorder(),
          color: color == Color.RED ? Colors.red : Colors.yellow,
        ),
      ),
    );
  }
}

              
            
!
999px

Console