Created
January 20, 2026 05:57
-
-
Save erbolamm/0bfb7a4b7f27d8749ffe2fc2f7e7b3d6 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/material.dart'; | |
| import 'dart:async'; | |
| import 'dart:math'; | |
| void main() { | |
| runApp(const ApliArtePacApp()); | |
| } | |
| class ApliArtePacApp extends StatelessWidget { | |
| const ApliArtePacApp({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return const MaterialApp( | |
| debugShowCheckedModeBanner: false, | |
| home: PacManGame(), | |
| ); | |
| } | |
| } | |
| // --- COLORES CORPORATIVOS APLIARTE --- | |
| class ApliArteColors { | |
| static const Color darkBlue = Color(0xFF164885); // Azul oscuro del logo | |
| static const Color lightBlue = Color(0xFF69B6E8); // Azul claro del logo | |
| static const Color darkGrey = Color(0xFF2F2F2F); // Gris oscuro/negro del logo | |
| static const Color background = Colors.black; // Fondo juego | |
| } | |
| // Estados del juego (Incluye Intro) | |
| enum GameState { intro, menu, playing, gameOver, win } | |
| // Direcciones | |
| enum Direction { right, down, left, up } | |
| class PacManGame extends StatefulWidget { | |
| const PacManGame({super.key}); | |
| @override | |
| State<PacManGame> createState() => _PacManGameState(); | |
| } | |
| class _PacManGameState extends State<PacManGame> with TickerProviderStateMixin { | |
| // Constantes del juego | |
| static const int mazeWidth = 19; | |
| static const int mazeHeight = 21; | |
| // Estados del juego | |
| GameState gameState = GameState.intro; // Empieza con la intro | |
| int score = 0; | |
| int lives = 3; | |
| int highScore = 0; | |
| double gridSize = 0; | |
| // Power pellet | |
| bool powerPelletActive = false; | |
| Timer? powerPelletTimer; | |
| int ghostsEaten = 0; | |
| // Laberinto | |
| List<List<int>> maze = []; | |
| final List<List<int>> originalMaze = [ | |
| [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
| [1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1], | |
| [1, 3, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 3, 1], | |
| [1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1], | |
| [1, 2, 1, 1, 1, 2, 1, 2, 1, 1, 1, 2, 1, 2, 1, 1, 1, 2, 1], | |
| [1, 2, 2, 2, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 2, 2, 2, 1], | |
| [1, 1, 1, 1, 1, 2, 1, 1, 0, 1, 0, 1, 1, 2, 1, 1, 1, 1, 1], | |
| [0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0], | |
| [1, 1, 1, 1, 1, 2, 1, 0, 1, 0, 1, 0, 1, 2, 1, 1, 1, 1, 1], | |
| [0, 0, 0, 0, 0, 2, 0, 0, 1, 0, 1, 0, 0, 2, 0, 0, 0, 0, 0], | |
| [1, 1, 1, 1, 1, 2, 1, 0, 1, 1, 1, 0, 1, 2, 1, 1, 1, 1, 1], | |
| [0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0], | |
| [1, 1, 1, 1, 1, 2, 1, 1, 0, 1, 0, 1, 1, 2, 1, 1, 1, 1, 1], | |
| [1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1], | |
| [1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1], | |
| [1, 3, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 3, 1], | |
| [1, 1, 1, 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 1, 1, 1], | |
| [1, 2, 2, 2, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 2, 2, 2, 1], | |
| [1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1], | |
| [1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1], | |
| [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
| ]; | |
| Player player = Player(); | |
| List<Ghost> ghosts = []; | |
| late AnimationController mouthController; | |
| late AnimationController introController; // Animación para la intro | |
| Timer? gameTimer; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| mouthController = AnimationController( | |
| duration: const Duration(milliseconds: 200), | |
| vsync: this, | |
| )..repeat(reverse: true); | |
| introController = AnimationController( | |
| duration: const Duration(seconds: 2), | |
| vsync: this, | |
| )..forward(); | |
| // Iniciar la secuencia de Intro | |
| startIntroSequence(); | |
| } | |
| void startIntroSequence() async { | |
| // Esperar 3.5 segundos en la intro antes de ir al menú | |
| await Future.delayed(const Duration(milliseconds: 3500)); | |
| if (mounted) { | |
| setState(() { | |
| gameState = GameState.menu; | |
| }); | |
| loadHighScore(); | |
| } | |
| } | |
| void initializeGame() { | |
| maze = originalMaze.map((row) => List<int>.from(row)).toList(); | |
| player = Player(); | |
| ghosts = [ | |
| Ghost(x: 9.0, y: 9.0, color: Colors.red, direction: Direction.up), | |
| Ghost(x: 8.0, y: 9.0, color: Colors.pinkAccent, direction: Direction.down), | |
| Ghost(x: 10.0, y: 9.0, color: Colors.cyanAccent, direction: Direction.left), | |
| Ghost(x: 9.0, y: 8.0, color: Colors.orange, direction: Direction.right), | |
| ]; | |
| powerPelletActive = false; | |
| powerPelletTimer?.cancel(); | |
| ghostsEaten = 0; | |
| } | |
| void loadHighScore() async { | |
| setState(() { | |
| highScore = 0; | |
| }); | |
| } | |
| void saveHighScore() async { | |
| if (score > highScore) { | |
| highScore = score; | |
| } | |
| } | |
| void startGame() { | |
| setState(() { | |
| gameState = GameState.playing; | |
| score = 0; | |
| lives = 3; | |
| }); | |
| initializeGame(); | |
| startGameLoop(); | |
| } | |
| void startGameLoop() { | |
| gameTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) { | |
| if (gameState == GameState.playing) { | |
| updateGame(); | |
| } | |
| }); | |
| } | |
| void updateGame() { | |
| movePlayer(); | |
| moveGhosts(); | |
| checkCollisions(); | |
| collectDots(); | |
| checkWinCondition(); | |
| setState(() {}); | |
| } | |
| void movePlayer() { | |
| double newX = player.x; | |
| double newY = player.y; | |
| switch (player.nextDirection) { | |
| case Direction.right: newX += 0.2; break; | |
| case Direction.down: newY += 0.2; break; | |
| case Direction.left: newX -= 0.2; break; | |
| case Direction.up: newY -= 0.2; break; | |
| } | |
| if (!isWall(newX, newY)) { | |
| player.direction = player.nextDirection; | |
| player.x = newX; | |
| player.y = newY; | |
| } else { | |
| newX = player.x; | |
| newY = player.y; | |
| switch (player.direction) { | |
| case Direction.right: newX += 0.2; break; | |
| case Direction.down: newY += 0.2; break; | |
| case Direction.left: newX -= 0.2; break; | |
| case Direction.up: newY -= 0.2; break; | |
| } | |
| if (!isWall(newX, newY)) { | |
| player.x = newX; | |
| player.y = newY; | |
| } | |
| } | |
| if (player.x < 0) player.x = mazeWidth - 1; | |
| if (player.x >= mazeWidth) player.x = 0; | |
| } | |
| void moveGhosts() { | |
| for (var ghost in ghosts) { | |
| if (ghost.isScared) { | |
| ghost.moveAway(maze, mazeWidth, mazeHeight, player.x, player.y); | |
| } else { | |
| ghost.move(maze, mazeWidth, mazeHeight); | |
| } | |
| } | |
| } | |
| bool isWall(double x, double y) { | |
| int gridX = x.round(); | |
| int gridY = y.round(); | |
| if (gridX < 0 || gridX >= mazeWidth || gridY < 0 || gridY >= mazeHeight) return true; | |
| return maze[gridY][gridX] == 1; | |
| } | |
| void collectDots() { | |
| int gridX = player.x.round(); | |
| int gridY = player.y.round(); | |
| if (gridX >= 0 && gridX < mazeWidth && gridY >= 0 && gridY < mazeHeight) { | |
| int cell = maze[gridY][gridX]; | |
| if (cell == 2) { | |
| maze[gridY][gridX] = 0; | |
| score += 10; | |
| } else if (cell == 3) { | |
| maze[gridY][gridX] = 0; | |
| score += 50; | |
| activatePowerPellet(); | |
| } | |
| if (score > highScore) highScore = score; | |
| } | |
| } | |
| void activatePowerPellet() { | |
| powerPelletActive = true; | |
| ghostsEaten = 0; | |
| for (var ghost in ghosts) { | |
| ghost.isVulnerable = true; | |
| ghost.isScared = true; | |
| } | |
| powerPelletTimer?.cancel(); | |
| powerPelletTimer = Timer(const Duration(seconds: 8), () { | |
| powerPelletActive = false; | |
| for (var ghost in ghosts) { | |
| ghost.isVulnerable = false; | |
| ghost.isScared = false; | |
| } | |
| if (mounted) setState(() {}); | |
| }); | |
| setState(() {}); | |
| } | |
| void checkCollisions() { | |
| for (var ghost in ghosts) { | |
| double distance = sqrt(pow(player.x - ghost.x, 2) + pow(player.y - ghost.y, 2)); | |
| if (distance < 0.8) { | |
| if (ghost.isVulnerable) { | |
| int points = 100 * pow(2, ghostsEaten).toInt(); | |
| score += points; | |
| ghostsEaten++; | |
| ghost.x = 9.0; ghost.y = 9.0; | |
| ghost.isVulnerable = false; ghost.isScared = false; | |
| if (score > highScore) highScore = score; | |
| } else { | |
| lives--; | |
| if (lives <= 0) { | |
| gameOver(); | |
| } else { | |
| player.x = 9.0; player.y = 15.0; | |
| player.direction = Direction.right; | |
| player.nextDirection = Direction.right; | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| void checkWinCondition() { | |
| int remainingDots = 0; | |
| for (var row in maze) { | |
| for (var cell in row) { | |
| if (cell == 2 || cell == 3) remainingDots++; | |
| } | |
| } | |
| if (remainingDots == 0) win(); | |
| } | |
| void gameOver() { | |
| setState(() { gameState = GameState.gameOver; }); | |
| gameTimer?.cancel(); | |
| saveHighScore(); | |
| } | |
| void win() { | |
| setState(() { gameState = GameState.win; }); | |
| gameTimer?.cancel(); | |
| saveHighScore(); | |
| } | |
| void resetGame() { | |
| gameTimer?.cancel(); | |
| setState(() { gameState = GameState.menu; }); | |
| } | |
| void setPlayerDirection(Direction direction) { | |
| if (gameState == GameState.playing) { | |
| player.nextDirection = direction; | |
| } | |
| } | |
| @override | |
| void dispose() { | |
| mouthController.dispose(); | |
| introController.dispose(); | |
| gameTimer?.cancel(); | |
| powerPelletTimer?.cancel(); | |
| super.dispose(); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| backgroundColor: ApliArteColors.background, | |
| body: SafeArea( | |
| child: buildGameScreen(), | |
| ), | |
| ); | |
| } | |
| Widget buildGameScreen() { | |
| switch (gameState) { | |
| case GameState.intro: | |
| return buildIntroScreen(); | |
| case GameState.menu: | |
| return buildMenuScreen(); | |
| case GameState.playing: | |
| return buildPlayingScreen(); | |
| case GameState.gameOver: | |
| return buildGameOverScreen(); | |
| case GameState.win: | |
| return buildWinScreen(); | |
| } | |
| } | |
| // --- PANTALLA INTRO APLIARTE --- | |
| Widget buildIntroScreen() { | |
| return FadeTransition( | |
| opacity: introController, | |
| child: Center( | |
| child: Column( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: [ | |
| // Simulación del logo con texto y colores | |
| Row( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: [ | |
| Icon(Icons.keyboard_double_arrow_up, size: 80, color: ApliArteColors.darkBlue), | |
| const SizedBox(width: 10), | |
| Icon(Icons.keyboard_double_arrow_up, size: 80, color: ApliArteColors.lightBlue), | |
| ], | |
| ), | |
| const SizedBox(height: 20), | |
| RichText( | |
| text: TextSpan( | |
| style: const TextStyle(fontSize: 40, fontWeight: FontWeight.bold, fontFamily: 'monospace'), | |
| children: [ | |
| TextSpan(text: 'Apli', style: TextStyle(color: ApliArteColors.lightBlue)), | |
| TextSpan(text: 'Arte', style: TextStyle(color: ApliArteColors.darkGrey)), | |
| ], | |
| ), | |
| ), | |
| const SizedBox(height: 10), | |
| const Text( | |
| 'GAMES', | |
| style: TextStyle( | |
| fontSize: 30, | |
| fontWeight: FontWeight.bold, | |
| letterSpacing: 8.0, | |
| color: ApliArteColors.darkBlue, | |
| fontFamily: 'monospace', | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| Widget buildMenuScreen() { | |
| return Center( | |
| child: Column( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: [ | |
| Text( | |
| 'PAC-MAN', | |
| style: TextStyle( | |
| fontSize: 48, | |
| fontWeight: FontWeight.bold, | |
| color: ApliArteColors.lightBlue, // Pacman azul ApliArte | |
| fontFamily: 'monospace', | |
| ), | |
| ), | |
| const SizedBox(height: 16), | |
| Text( | |
| 'EDICIÓN APLIARTE', | |
| style: TextStyle( | |
| fontSize: 20, | |
| color: ApliArteColors.darkBlue, | |
| fontFamily: 'monospace', | |
| ), | |
| ), | |
| const SizedBox(height: 32), | |
| Text( | |
| 'Récord: $highScore', | |
| style: const TextStyle( | |
| fontSize: 18, | |
| color: Colors.white, | |
| fontFamily: 'monospace', | |
| ), | |
| ), | |
| const SizedBox(height: 32), | |
| ElevatedButton( | |
| onPressed: startGame, | |
| style: ElevatedButton.styleFrom( | |
| backgroundColor: ApliArteColors.lightBlue, | |
| foregroundColor: ApliArteColors.darkGrey, | |
| padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), | |
| textStyle: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), | |
| ), | |
| child: const Text('JUGAR'), | |
| ), | |
| const SizedBox(height: 16), | |
| const Text( | |
| 'Usa los controles para moverte', | |
| style: TextStyle(fontSize: 14, color: Colors.grey, fontFamily: 'monospace'), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| Widget buildPlayingScreen() { | |
| return Column( | |
| children: [ | |
| Padding( | |
| padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), | |
| child: Row( | |
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| children: [ | |
| IconButton( | |
| icon: const Icon(Icons.arrow_back, color: ApliArteColors.lightBlue), | |
| onPressed: () { | |
| setState(() => gameState = GameState.menu); | |
| }, | |
| ), | |
| Column( | |
| children: [ | |
| Text('PUNTOS', style: TextStyle(fontSize: 12, color: ApliArteColors.lightBlue, fontFamily: 'monospace')), | |
| Text('$score', style: const TextStyle(fontSize: 20, color: Colors.white, fontFamily: 'monospace')), | |
| ], | |
| ), | |
| Column( | |
| children: [ | |
| Text('RÉCORD', style: TextStyle(fontSize: 12, color: ApliArteColors.darkBlue, fontFamily: 'monospace')), | |
| Text('$highScore', style: const TextStyle(fontSize: 20, color: Colors.white, fontFamily: 'monospace')), | |
| ], | |
| ), | |
| Row( | |
| children: List.generate(lives, (index) => const Icon(Icons.favorite, color: Colors.red, size: 20)), | |
| ), | |
| ], | |
| ), | |
| ), | |
| Expanded( | |
| child: Center( | |
| child: LayoutBuilder( | |
| builder: (context, constraints) { | |
| double maxWidth = constraints.maxWidth; | |
| double maxHeight = constraints.maxHeight; | |
| gridSize = min(maxWidth / mazeWidth, maxHeight / mazeHeight); | |
| return Container( | |
| width: gridSize * mazeWidth, | |
| height: gridSize * mazeHeight, | |
| decoration: BoxDecoration( | |
| border: Border.all(color: ApliArteColors.darkBlue, width: 2), | |
| ), | |
| child: CustomPaint( | |
| painter: GamePainter( | |
| maze: maze, | |
| player: player, | |
| ghosts: ghosts, | |
| mouthAnimation: mouthController, | |
| gridSize: gridSize, | |
| ), | |
| size: Size(gridSize * mazeWidth, gridSize * mazeHeight), | |
| ), | |
| ); | |
| }, | |
| ), | |
| ), | |
| ), | |
| buildControls(), | |
| ], | |
| ); | |
| } | |
| Widget buildControls() { | |
| return Container( | |
| padding: const EdgeInsets.all(16), | |
| child: Column( | |
| children: [ | |
| ElevatedButton( | |
| onPressed: () => setPlayerDirection(Direction.up), | |
| style: ElevatedButton.styleFrom( | |
| backgroundColor: ApliArteColors.darkGrey, | |
| shape: const CircleBorder(), | |
| padding: const EdgeInsets.all(20), | |
| ), | |
| child: const Icon(Icons.keyboard_arrow_up, color: ApliArteColors.lightBlue, size: 30), | |
| ), | |
| Row( | |
| mainAxisAlignment: MainAxisAlignment.spaceEvenly, | |
| children: [ | |
| ElevatedButton( | |
| onPressed: () => setPlayerDirection(Direction.left), | |
| style: ElevatedButton.styleFrom( | |
| backgroundColor: ApliArteColors.darkGrey, | |
| shape: const CircleBorder(), | |
| padding: const EdgeInsets.all(20), | |
| ), | |
| child: const Icon(Icons.keyboard_arrow_left, color: ApliArteColors.lightBlue, size: 30), | |
| ), | |
| const SizedBox(width: 40), | |
| ElevatedButton( | |
| onPressed: () => setPlayerDirection(Direction.right), | |
| style: ElevatedButton.styleFrom( | |
| backgroundColor: ApliArteColors.darkGrey, | |
| shape: const CircleBorder(), | |
| padding: const EdgeInsets.all(20), | |
| ), | |
| child: const Icon(Icons.keyboard_arrow_right, color: ApliArteColors.lightBlue, size: 30), | |
| ), | |
| ], | |
| ), | |
| ElevatedButton( | |
| onPressed: () => setPlayerDirection(Direction.down), | |
| style: ElevatedButton.styleFrom( | |
| backgroundColor: ApliArteColors.darkGrey, | |
| shape: const CircleBorder(), | |
| padding: const EdgeInsets.all(20), | |
| ), | |
| child: const Icon(Icons.keyboard_arrow_down, color: ApliArteColors.lightBlue, size: 30), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| Widget buildGameOverScreen() { | |
| return Center( | |
| child: Column( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: [ | |
| const Text( | |
| 'FIN DEL JUEGO', | |
| style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold, color: Colors.red, fontFamily: 'monospace'), | |
| ), | |
| const SizedBox(height: 16), | |
| Text( | |
| 'Puntaje Final: $score', | |
| style: TextStyle(fontSize: 24, color: ApliArteColors.lightBlue, fontFamily: 'monospace'), | |
| ), | |
| const SizedBox(height: 32), | |
| ElevatedButton( | |
| onPressed: resetGame, | |
| style: ElevatedButton.styleFrom(backgroundColor: Colors.red, padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16)), | |
| child: const Text('REINTENTAR', style: TextStyle(fontSize: 18, color: Colors.white)), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| Widget buildWinScreen() { | |
| return Center( | |
| child: Column( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: [ | |
| Text( | |
| '¡GANASTE!', | |
| style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold, color: ApliArteColors.lightBlue, fontFamily: 'monospace'), | |
| ), | |
| const SizedBox(height: 16), | |
| Text( | |
| 'Puntaje Final: $score', | |
| style: const TextStyle(fontSize: 24, color: Colors.white, fontFamily: 'monospace'), | |
| ), | |
| const SizedBox(height: 32), | |
| ElevatedButton( | |
| onPressed: resetGame, | |
| style: ElevatedButton.styleFrom(backgroundColor: ApliArteColors.lightBlue, padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16)), | |
| child: Text('JUGAR DE NUEVO', style: TextStyle(fontSize: 18, color: ApliArteColors.darkGrey)), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| class Player { | |
| double x = 9.0; | |
| double y = 15.0; | |
| Direction direction = Direction.right; | |
| Direction nextDirection = Direction.right; | |
| } | |
| class Ghost { | |
| double x; | |
| double y; | |
| Color color; | |
| Direction direction; | |
| Random random = Random(); | |
| bool isVulnerable = false; | |
| bool isScared = false; | |
| Ghost({required this.x, required this.y, required this.color, required this.direction}); | |
| void move(List<List<int>> maze, int mazeWidth, int mazeHeight) { | |
| List<Direction> possibleDirections = []; | |
| if (x > 0 && maze[y.round()][(x - 0.3).round()] != 1) possibleDirections.add(Direction.left); | |
| if (x < mazeWidth - 1 && maze[y.round()][(x + 0.3).round()] != 1) possibleDirections.add(Direction.right); | |
| if (y > 0 && maze[(y - 0.3).round()][x.round()] != 1) possibleDirections.add(Direction.up); | |
| if (y < mazeHeight - 1 && maze[(y + 0.3).round()][x.round()] != 1) possibleDirections.add(Direction.down); | |
| if (!possibleDirections.contains(direction) || random.nextInt(20) == 0) { | |
| if (possibleDirections.isNotEmpty) direction = possibleDirections[random.nextInt(possibleDirections.length)]; | |
| } | |
| switch (direction) { | |
| case Direction.right: if (possibleDirections.contains(Direction.right)) x += 0.15; break; | |
| case Direction.down: if (possibleDirections.contains(Direction.down)) y += 0.15; break; | |
| case Direction.left: if (possibleDirections.contains(Direction.left)) x -= 0.15; break; | |
| case Direction.up: if (possibleDirections.contains(Direction.up)) y -= 0.15; break; | |
| } | |
| if (x < 0) x = mazeWidth - 1; | |
| if (x >= mazeWidth) x = 0; | |
| } | |
| void moveAway(List<List<int>> maze, int mazeWidth, int mazeHeight, double playerX, double playerY) { | |
| List<Direction> possibleDirections = []; | |
| if (x > 0 && maze[y.round()][(x - 0.3).round()] != 1) possibleDirections.add(Direction.left); | |
| if (x < mazeWidth - 1 && maze[y.round()][(x + 0.3).round()] != 1) possibleDirections.add(Direction.right); | |
| if (y > 0 && maze[(y - 0.3).round()][x.round()] != 1) possibleDirections.add(Direction.up); | |
| if (y < mazeHeight - 1 && maze[(y + 0.3).round()][x.round()] != 1) possibleDirections.add(Direction.down); | |
| if (possibleDirections.isEmpty) return; | |
| Direction bestDirection = possibleDirections[0]; | |
| double maxDistance = 0; | |
| for (var dir in possibleDirections) { | |
| double testX = x, testY = y; | |
| switch (dir) { | |
| case Direction.left: testX -= 0.15; break; | |
| case Direction.right: testX += 0.15; break; | |
| case Direction.up: testY -= 0.15; break; | |
| case Direction.down: testY += 0.15; break; | |
| } | |
| double distance = sqrt(pow(testX - playerX, 2) + pow(testY - playerY, 2)); | |
| if (distance > maxDistance) { | |
| maxDistance = distance; | |
| bestDirection = dir; | |
| } | |
| } | |
| direction = bestDirection; | |
| switch (direction) { | |
| case Direction.right: x += 0.15; break; | |
| case Direction.down: y += 0.15; break; | |
| case Direction.left: x -= 0.15; break; | |
| case Direction.up: y -= 0.15; break; | |
| } | |
| if (x < 0) x = mazeWidth - 1; | |
| if (x >= mazeWidth) x = 0; | |
| } | |
| } | |
| class GamePainter extends CustomPainter { | |
| final List<List<int>> maze; | |
| final Player player; | |
| final List<Ghost> ghosts; | |
| final AnimationController mouthAnimation; | |
| final double gridSize; | |
| GamePainter({required this.maze, required this.player, required this.ghosts, required this.mouthAnimation, required this.gridSize}); | |
| @override | |
| void paint(Canvas canvas, Size size) { | |
| drawMaze(canvas); | |
| drawGhosts(canvas); | |
| drawPlayer(canvas); | |
| } | |
| void drawMaze(Canvas canvas) { | |
| for (int y = 0; y < maze.length; y++) { | |
| for (int x = 0; x < maze[y].length; x++) { | |
| double pixelX = x * gridSize; | |
| double pixelY = y * gridSize; | |
| switch (maze[y][x]) { | |
| case 1: // Pared (USANDO AZUL OSCURO APLIARTE) | |
| Paint wallPaint = Paint()..color = ApliArteColors.darkBlue; | |
| canvas.drawRect(Rect.fromLTWH(pixelX, pixelY, gridSize, gridSize), wallPaint); | |
| break; | |
| case 2: // Punto pequeño | |
| Paint dotPaint = Paint()..color = Colors.white.withOpacity(0.5); | |
| canvas.drawCircle(Offset(pixelX + gridSize / 2, pixelY + gridSize / 2), gridSize * 0.12, dotPaint); | |
| break; | |
| case 3: // Power pellet | |
| Paint pelletPaint = Paint()..color = Colors.white; | |
| canvas.drawCircle(Offset(pixelX + gridSize / 2, pixelY + gridSize / 2), gridSize * 0.32, pelletPaint); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| void drawPlayer(Canvas canvas) { | |
| double pixelX = player.x * gridSize + gridSize / 2; | |
| double pixelY = player.y * gridSize + gridSize / 2; | |
| // PACMAN COLOR APLIARTE (Azul Claro) | |
| Paint playerPaint = Paint()..color = ApliArteColors.lightBlue; | |
| canvas.drawCircle(Offset(pixelX, pixelY), gridSize / 2 - 2, playerPaint); | |
| Paint mouthPaint = Paint()..color = Colors.black; | |
| double mouthAngle = mouthAnimation.value * 60; | |
| double startAngle = 0; | |
| switch (player.direction) { | |
| case Direction.right: startAngle = -mouthAngle / 2 * (pi / 180); break; | |
| case Direction.down: startAngle = (90 - mouthAngle / 2) * (pi / 180); break; | |
| case Direction.left: startAngle = (180 - mouthAngle / 2) * (pi / 180); break; | |
| case Direction.up: startAngle = (270 - mouthAngle / 2) * (pi / 180); break; | |
| } | |
| canvas.drawArc(Rect.fromCircle(center: Offset(pixelX, pixelY), radius: gridSize / 2 - 2), startAngle, mouthAngle * (pi / 180), true, mouthPaint); | |
| } | |
| void drawGhosts(Canvas canvas) { | |
| for (var ghost in ghosts) { | |
| double pixelX = ghost.x * gridSize; | |
| double pixelY = ghost.y * gridSize; | |
| Color ghostColor = ghost.isVulnerable ? ApliArteColors.darkBlue.withOpacity(0.7) : ghost.color; // Fantasmas vulnerables se ponen azul oscuro | |
| Paint ghostPaint = Paint()..color = ghostColor; | |
| Path ghostPath = Path(); | |
| ghostPath.addRRect(RRect.fromRectAndCorners(Rect.fromLTWH(pixelX + 2, pixelY + 2, gridSize - 4, gridSize - 4), topLeft: Radius.circular(gridSize / 2), topRight: Radius.circular(gridSize / 2))); | |
| canvas.drawPath(ghostPath, ghostPaint); | |
| Paint eyePaint = Paint()..color = Colors.white; | |
| Paint pupilPaint = Paint()..color = Colors.black; | |
| canvas.drawCircle(Offset(pixelX + gridSize * 0.32, pixelY + gridSize * 0.32), gridSize * 0.12, eyePaint); | |
| canvas.drawCircle(Offset(pixelX + gridSize * 0.32, pixelY + gridSize * 0.32), gridSize * 0.06, pupilPaint); | |
| canvas.drawCircle(Offset(pixelX + gridSize * 0.68, pixelY + gridSize * 0.32), gridSize * 0.12, eyePaint); | |
| canvas.drawCircle(Offset(pixelX + gridSize * 0.68, pixelY + gridSize * 0.32), gridSize * 0.06, pupilPaint); | |
| } | |
| } | |
| @override | |
| bool shouldRepaint(CustomPainter oldDelegate) => true; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment