comment mettre en place le fait de pouvoir perdre sur le snake en flutter/dart, garde une architecture clean : import 'dart:math';

import 'package:flutter_snake/models/food_model.dart';
import 'package:flutter_snake/models/snake_model.dart';

class GameModel {
  static const int NB_CASES = 140;
  static const int NB_LIGNES = 14;
  static const int NB_COLONNES = 10;

  static const int DIRECTION_HAUT = 0;
  static const int DIRECTION_DROITE = 1;
  static const int DIRECTION_BAS = 2;
  static const int DIRECTION_GAUCHE = 3;

  static const int SNAKE_HEAD = 1;
  static const int SNAKE_BODY = 2;
  static const int FOOD = 3;

  int score = 0;
  int currentDirection = DIRECTION_DROITE;


  List<List<int>> grid = List.generate(NB_LIGNES, (i) => List.filled(NB_COLONNES, 0));

  late FoodModel foodModel;
  late SnakeModel snakeModel;

  GameModel(){
    foodModel = FoodModel(gameModel: this);
    snakeModel = SnakeModel(gameModel: this);
  }
  // Add your class properties and methods here

  void start(){
    // on réinitialise la matrice
    for(int i = 0; i < NB_LIGNES; i++){
      for(int j = 0; j < NB_COLONNES; j++){
        grid[i][j] = 0;
      }
    }
    foodModel.createFood();
    snakeModel.reset();
  }

  static List<int> getRandomCoordinates(){
    Random random = Random();
    int randomX = random.nextInt(NB_COLONNES);
    int randomY = random.nextInt(NB_LIGNES);
    print("randomX: $randomX, randomY: $randomY");
    return [randomX, randomY];
  }

  void changeDirection(int newDirection){
    currentDirection = newDirection;
    moveSnake();
  }

  void moveSnake(){
    snakeModel.moveSnake(currentDirection);
  }

  bool isFood(int x, int y){
    return grid[y][x] == FOOD;
  }

  bool isInGrid(int x, int y){
    return x >= 0 && x < NB_COLONNES && y >= 0 && y < NB_LIGNES;
  }

  void increaseScore(){
    score++;
  }

  void eatFood(){}
}import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_snake/models/game_model.dart';

class SnakePage extends StatefulWidget {
  const SnakePage({Key? key}) : super(key: key);

  @override
  State<SnakePage> createState() => _SnakePageState();
}

class _SnakePageState extends State<SnakePage> {
  GameModel gameModel = GameModel();
  Timer? timer;

  @override
  void initState() {
    // TODO: implement initState
    print("start");
    gameModel.start();
    timer = Timer.periodic(Duration(milliseconds: 500), (timer) {
      setState(() {
        gameModel.moveSnake();
      });
    });
  }

  void resetTimer() {
    timer?.cancel();
    timer = Timer.periodic(Duration(milliseconds: 500), (timer) {
      setState(() {
        gameModel.moveSnake();
      });
    });
  }

  @override
  void dispose() {
    // TODO: implement dispose
    timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.arrow_back),
          onPressed: () {
            // TODO: Implement your back button logic here
            Navigator.pop(context);
          },
        ),
        title: Text('Snake Game. Score: ' + gameModel.score.toString()),
        actions: <Widget>[
          PopupMenuButton<String>(
            onSelected: (String result) {
              // TODO: Implement your menu actions here
            },
            itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
              const PopupMenuItem<String>(
                value: 'Pause',
                child: Text('Pause'),
              ),
              const PopupMenuItem<String>(
                value: 'Replay',
                child: Text('Replay'),
              ),
            ],
          ),
        ],
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            flex: 7,
            child: Container(
              color: Colors.green,
              child: GridView.builder(
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 10, // Change this number as per your need
                ),
                itemBuilder: (BuildContext context, int index) {
                  int y = index ~/ GameModel.NB_COLONNES;
                  int x = index - ((index ~/ GameModel.NB_COLONNES) * GameModel.NB_COLONNES);

                  Color cellColor;

                  switch (gameModel.grid[y][x]) {
                    case GameModel.SNAKE_HEAD:
                      cellColor = Colors.yellow;
                      break;
                    case GameModel.SNAKE_BODY:
                      cellColor = Colors.green;
                      break;
                    case GameModel.FOOD:
                      print(index.toString() + " " + x.toString() + " " + y.toString());
                      cellColor = Colors.red;
                      break;
                    default:
                      cellColor = Colors.lightGreen;
                  }

                  return GridTile(
                    child: Container(
                      decoration: BoxDecoration(
                        color: cellColor,
                        border: Border.all(color: Colors.white),
                      ),
                      // TODO: Add your game cell here
                    ),
                  );
                },
                itemCount:
                    GameModel.NB_CASES, // Change this number as per your need
              ),
            ),
          ),
          Expanded(
            flex: 2,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                ElevatedButton(
                  onPressed: () {
                    // TODO: Implement left direction logic
                    setState(() {
                      resetTimer();
                      gameModel.changeDirection(GameModel.DIRECTION_GAUCHE);
                    });
                  },
                  child: Icon(Icons.arrow_left),
                ),
                Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    ElevatedButton(
                      onPressed: () {
                        // TODO: Implement up direction logic
                        setState(() {
                          resetTimer();
                          gameModel.changeDirection(GameModel.DIRECTION_HAUT);
                        });
                      },
                      child: Icon(Icons.arrow_upward),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        // TODO: Implement down direction logic
                        setState(() {
                          resetTimer();
                          gameModel.changeDirection(GameModel.DIRECTION_BAS);
                        });
                      },
                      child: Icon(Icons.arrow_downward),
                    ),
                  ],
                ),
                ElevatedButton(
                  onPressed: () {
                    // TODO: Implement right direction logic
                    setState(() {
                      resetTimer();
                      gameModel.changeDirection(GameModel.DIRECTION_DROITE);
                    });
                  },
                  child: Icon(Icons.arrow_right),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
import 'dart:ffi';

import 'package:flutter_snake/models/game_model.dart';

class FoodModel{

  GameModel gameModel;

  FoodModel({required this.gameModel});


  void createFood(){
    List<int> coordinates = GameModel.getRandomCoordinates();
    gameModel.grid[coordinates[1]][coordinates[0]] = GameModel.FOOD;
  }
}import 'package:flutter_snake/models/game_model.dart';

class SnakeModel{
  int x;
  int y;
  int size = 1;
  GameModel gameModel;

  SnakeModel({required this.gameModel, this.x = 0, this.y = 0});

  void reset(){
    x = GameModel.NB_COLONNES ~/ 2;
    y = GameModel.NB_LIGNES ~/ 2;
    size = 2;
    displaySnake();
  }

  void displaySnake(){
    if (gameModel.isFood(x, y)){
      growSnake();
      gameModel.increaseScore();
      gameModel.foodModel.createFood();
    }
    gameModel.grid[y][x] = GameModel.SNAKE_HEAD;
    print("new snake head: x: $x, y: $y");
  }

  void moveSnake(int direction){

    switch(direction){
      case GameModel.DIRECTION_HAUT:
        if (gameModel.isInGrid(x, y-1)){
          gameModel.grid[y][x] = 0;
          y--;
        }
        break;
      case GameModel.DIRECTION_DROITE:
        if (gameModel.isInGrid(x+1, y)){
          gameModel.grid[y][x] = 0;
          x++;
        }
        break;
      case GameModel.DIRECTION_BAS:
        if (gameModel.isInGrid(x, y+1)){
          gameModel.grid[y][x] = 0;
          y++;
        }
        break;
      case GameModel.DIRECTION_GAUCHE:
        if (gameModel.isInGrid(x-1, y)){
          gameModel.grid[y][x] = 0;
          x--;
        }
        break;
    }

    displaySnake();
  }

  void growSnake(){
    size++;
  }
}