diff --git a/lib/classes/coordinate_2d.dart b/lib/classes/coordinate_2d.dart new file mode 100644 index 0000000..5a45f4e --- /dev/null +++ b/lib/classes/coordinate_2d.dart @@ -0,0 +1,54 @@ +import 'dart:math' as math; + +/// A robust, immutable 2D coordinate. +class Coordinate2D implements Comparable { + final double x; + final double y; + + const Coordinate2D(this.x, this.y); + + /// Factory for a (0,0) coordinate. + static const Coordinate2D zero = Coordinate2D(0.0, 0.0); + + // --- Core Logic --- + + /// Returns the Euclidean distance between this and [other]. + /// Uses the formula: $$d = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}$$ + double distanceTo(Coordinate2D other) { + final dx = x - other.x; + final dy = y - other.y; + return math.sqrt(dx * dx + dy * dy); + } + + /// True if both ordinates are finite (not NaN or Infinity). + bool get isValid => x.isFinite && y.isFinite; + + Coordinate2D operator +(Coordinate2D other) => + Coordinate2D(x + other.x, y + other.y); + Coordinate2D operator -(Coordinate2D other) => + Coordinate2D(x - other.x, y - other.y); + Coordinate2D operator *(double factor) => + Coordinate2D(x * factor, y * factor); + Coordinate2D operator /(double divisor) => + Coordinate2D(x / divisor, y / divisor); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Coordinate2D && + runtimeType == other.runtimeType && + x == other.x && + y == other.y; + + @override + int get hashCode => Object.hash(x, y); + + @override + int compareTo(Coordinate2D other) { + if (x != other.x) return x.compareTo(other.x); + return y.compareTo(other.y); + } + + @override + String toString() => 'Coordinate($x, $y)'; +} diff --git a/lib/classes/linear_coordinates.dart b/lib/classes/linear_coordinates.dart deleted file mode 100644 index 65eafc5..0000000 --- a/lib/classes/linear_coordinates.dart +++ /dev/null @@ -1 +0,0 @@ -typedef LinearCoordinates = ({double x, double y}); diff --git a/lib/features/entities/enemies/brown_guard.dart b/lib/features/entities/enemies/brown_guard.dart index 4290755..77a4e86 100644 --- a/lib/features/entities/enemies/brown_guard.dart +++ b/lib/features/entities/enemies/brown_guard.dart @@ -1,6 +1,6 @@ import 'dart:math' as math; -import 'package:wolf_dart/classes/linear_coordinates.dart'; +import 'package:wolf_dart/classes/coordinate_2d.dart'; import 'package:wolf_dart/features/entities/enemies/enemy.dart'; import 'package:wolf_dart/features/entities/entity.dart'; @@ -56,19 +56,20 @@ class BrownGuard extends Enemy { @override void update({ required int elapsedMs, - required LinearCoordinates player, + required Coordinate2D playerPosition, required bool Function(int x, int y) isWalkable, required void Function(int damage) onDamagePlayer, }) { // 1. Wake up logic - if (state == EntityState.idle && hasLineOfSight(player, isWalkable)) { + if (state == EntityState.idle && + hasLineOfSight(playerPosition, isWalkable)) { state = EntityState.patrolling; lastActionTime = elapsedMs; } // 2. Pre-calculate angles (needed for almost all states) - double dx = player.x - x; - double dy = player.y - y; + double dx = playerPosition.x - x; + double dy = playerPosition.y - y; double distance = math.sqrt(dx * dx + dy * dy); double angleToPlayer = math.atan2(dy, dx); @@ -107,7 +108,7 @@ class BrownGuard extends Enemy { spriteIndex = 58 + (walkFrame * 8) + octant; // Shooting transition if (distance < 5.0 && elapsedMs - lastActionTime > 2000) { - if (hasLineOfSight(player, isWalkable)) { + if (hasLineOfSight(playerPosition, isWalkable)) { state = EntityState.shooting; lastActionTime = elapsedMs; _hasFiredThisCycle = false; diff --git a/lib/features/entities/enemies/dog.dart b/lib/features/entities/enemies/dog.dart index ac9044e..7aa4b1a 100644 --- a/lib/features/entities/enemies/dog.dart +++ b/lib/features/entities/enemies/dog.dart @@ -1,6 +1,6 @@ import 'dart:math' as math; -import 'package:wolf_dart/classes/linear_coordinates.dart'; +import 'package:wolf_dart/classes/coordinate_2d.dart'; import 'package:wolf_dart/features/entities/enemies/enemy.dart'; import 'package:wolf_dart/features/entities/entity.dart'; @@ -49,12 +49,12 @@ class Dog extends Enemy { @override void update({ required int elapsedMs, - required LinearCoordinates player, + required Coordinate2D playerPosition, required bool Function(int x, int y) isWalkable, required void Function(int damage) onDamagePlayer, }) { if (state == EntityState.idle) { - if (hasLineOfSight(player, isWalkable)) { + if (hasLineOfSight(playerPosition, isWalkable)) { state = EntityState.patrolling; lastActionTime = elapsedMs; } @@ -65,8 +65,8 @@ class Dog extends Enemy { state == EntityState.shooting) { // "Shooting" here means biting - double dx = player.x - x; - double dy = player.y - y; + double dx = playerPosition.x - x; + double dy = playerPosition.y - y; double distance = math.sqrt(dx * dx + dy * dy); double angleToPlayer = math.atan2(dy, dx); diff --git a/lib/features/entities/enemies/enemy.dart b/lib/features/entities/enemies/enemy.dart index 8f84eac..9e239b4 100644 --- a/lib/features/entities/enemies/enemy.dart +++ b/lib/features/entities/enemies/enemy.dart @@ -1,6 +1,6 @@ import 'dart:math' as math; -import 'package:wolf_dart/classes/linear_coordinates.dart'; +import 'package:wolf_dart/classes/coordinate_2d.dart'; import 'package:wolf_dart/features/entities/entity.dart'; abstract class Enemy extends Entity { @@ -58,11 +58,11 @@ abstract class Enemy extends Entity { // The enemy can now check its own line of sight! bool hasLineOfSight( - LinearCoordinates player, + Coordinate2D playerPosition, bool Function(int x, int y) isWalkable, ) { - double dx = player.x - x; - double dy = player.y - y; + double dx = playerPosition.x - x; + double dy = playerPosition.y - y; double distance = math.sqrt(dx * dx + dy * dy); // 1. FOV Check @@ -94,7 +94,7 @@ abstract class Enemy extends Entity { // The weapon asks the enemy if it is unobstructed from the shooter bool hasLineOfSightFrom( - LinearCoordinates source, + Coordinate2D source, double sourceAngle, double distance, bool Function(int x, int y) isWalkable, @@ -112,7 +112,7 @@ abstract class Enemy extends Entity { void update({ required int elapsedMs, - required LinearCoordinates player, + required Coordinate2D playerPosition, required bool Function(int x, int y) isWalkable, required void Function(int damage) onDamagePlayer, }); diff --git a/lib/features/player/player.dart b/lib/features/player/player.dart index 90a3ab5..a38b7d3 100644 --- a/lib/features/player/player.dart +++ b/lib/features/player/player.dart @@ -1,6 +1,6 @@ import 'dart:math' as math; -import 'package:wolf_dart/classes/linear_coordinates.dart'; +import 'package:wolf_dart/classes/coordinate_2d.dart'; import 'package:wolf_dart/features/entities/collectible.dart'; import 'package:wolf_dart/features/entities/entity.dart'; import 'package:wolf_dart/features/weapon/weapon.dart'; @@ -55,7 +55,7 @@ class Player { } // Helper getter to interface with the RaycasterPainter - LinearCoordinates get position => (x: x, y: y); + Coordinate2D get position => Coordinate2D(x, y); // --- Weapon Switching & Animation Logic --- diff --git a/lib/features/renderer/renderer.dart b/lib/features/renderer/renderer.dart index 6174990..cf1f08e 100644 --- a/lib/features/renderer/renderer.dart +++ b/lib/features/renderer/renderer.dart @@ -2,7 +2,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; -import 'package:wolf_dart/classes/linear_coordinates.dart'; +import 'package:wolf_dart/classes/coordinate_2d.dart'; import 'package:wolf_dart/classes/matrix.dart'; import 'package:wolf_dart/features/difficulty/difficulty.dart'; import 'package:wolf_dart/features/entities/collectible.dart'; @@ -146,7 +146,7 @@ class _WolfRendererState extends State pX >= currentLevel[0].length || currentLevel[pY][pX] > 0) { double shortestDist = double.infinity; - LinearCoordinates nearestSafeSpot = (x: 1.5, y: 1.5); + Coordinate2D nearestSafeSpot = Coordinate2D(1.5, 1.5); for (int y = 0; y < currentLevel.length; y++) { for (int x = 0; x < currentLevel[y].length; x++) { @@ -159,7 +159,7 @@ class _WolfRendererState extends State if (dist < shortestDist) { shortestDist = dist; - nearestSafeSpot = (x: safeX, y: safeY); + nearestSafeSpot = Coordinate2D(safeX, safeY); } } } @@ -186,7 +186,7 @@ class _WolfRendererState extends State // 2. Explicit State Updates player.updateWeaponSwitch(); - _applyMovementAndCollision(movement.dx, movement.dy); + _applyMovementAndCollision(movement.x, movement.y); _updateEntities(elapsed); // Explicit reassignment from a pure(r) function @@ -209,7 +209,7 @@ class _WolfRendererState extends State } // Returns movement deltas instead of modifying class variables - ({double dx, double dy}) _processInputs(Duration elapsed) { + Coordinate2D _processInputs(Duration elapsed) { inputManager.update(); const double moveSpeed = 0.16; @@ -248,7 +248,7 @@ class _WolfRendererState extends State ); } - return (dx: dx, dy: dy); + return Coordinate2D(dx, dy); } // Now receives dx and dy explicitly @@ -277,7 +277,7 @@ class _WolfRendererState extends State if (entity is Enemy) { entity.update( elapsedMs: elapsed.inMilliseconds, - player: player.position, + playerPosition: player.position, isWalkable: _isWalkable, onDamagePlayer: _takeDamage, ); diff --git a/lib/features/weapon/weapon.dart b/lib/features/weapon/weapon.dart index 04ec3ca..200990f 100644 --- a/lib/features/weapon/weapon.dart +++ b/lib/features/weapon/weapon.dart @@ -1,6 +1,6 @@ import 'dart:math' as math; -import 'package:wolf_dart/classes/linear_coordinates.dart'; +import 'package:wolf_dart/classes/coordinate_2d.dart'; import 'package:wolf_dart/features/entities/enemies/enemy.dart'; import 'package:wolf_dart/features/entities/entity.dart'; @@ -83,7 +83,7 @@ abstract class Weapon { double threshold = 0.2 / dist; if (angleDiff.abs() < threshold) { - LinearCoordinates source = (x: playerX, y: playerY); + Coordinate2D source = Coordinate2D(playerX, playerY); // Delegate to the enemy to check if it's visible if (entity.hasLineOfSightFrom(