Refactor coordinate system

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-14 00:49:31 +01:00
parent 5c9dafbbdf
commit 46712370a4
8 changed files with 83 additions and 29 deletions

View File

@@ -0,0 +1,54 @@
import 'dart:math' as math;
/// A robust, immutable 2D coordinate.
class Coordinate2D implements Comparable<Coordinate2D> {
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)';
}

View File

@@ -1 +0,0 @@
typedef LinearCoordinates = ({double x, double y});

View File

@@ -1,6 +1,6 @@
import 'dart:math' as math; 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/enemies/enemy.dart';
import 'package:wolf_dart/features/entities/entity.dart'; import 'package:wolf_dart/features/entities/entity.dart';
@@ -56,19 +56,20 @@ class BrownGuard extends Enemy {
@override @override
void update({ void update({
required int elapsedMs, required int elapsedMs,
required LinearCoordinates player, required Coordinate2D playerPosition,
required bool Function(int x, int y) isWalkable, required bool Function(int x, int y) isWalkable,
required void Function(int damage) onDamagePlayer, required void Function(int damage) onDamagePlayer,
}) { }) {
// 1. Wake up logic // 1. Wake up logic
if (state == EntityState.idle && hasLineOfSight(player, isWalkable)) { if (state == EntityState.idle &&
hasLineOfSight(playerPosition, isWalkable)) {
state = EntityState.patrolling; state = EntityState.patrolling;
lastActionTime = elapsedMs; lastActionTime = elapsedMs;
} }
// 2. Pre-calculate angles (needed for almost all states) // 2. Pre-calculate angles (needed for almost all states)
double dx = player.x - x; double dx = playerPosition.x - x;
double dy = player.y - y; double dy = playerPosition.y - y;
double distance = math.sqrt(dx * dx + dy * dy); double distance = math.sqrt(dx * dx + dy * dy);
double angleToPlayer = math.atan2(dy, dx); double angleToPlayer = math.atan2(dy, dx);
@@ -107,7 +108,7 @@ class BrownGuard extends Enemy {
spriteIndex = 58 + (walkFrame * 8) + octant; spriteIndex = 58 + (walkFrame * 8) + octant;
// Shooting transition // Shooting transition
if (distance < 5.0 && elapsedMs - lastActionTime > 2000) { if (distance < 5.0 && elapsedMs - lastActionTime > 2000) {
if (hasLineOfSight(player, isWalkable)) { if (hasLineOfSight(playerPosition, isWalkable)) {
state = EntityState.shooting; state = EntityState.shooting;
lastActionTime = elapsedMs; lastActionTime = elapsedMs;
_hasFiredThisCycle = false; _hasFiredThisCycle = false;

View File

@@ -1,6 +1,6 @@
import 'dart:math' as math; 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/enemies/enemy.dart';
import 'package:wolf_dart/features/entities/entity.dart'; import 'package:wolf_dart/features/entities/entity.dart';
@@ -49,12 +49,12 @@ class Dog extends Enemy {
@override @override
void update({ void update({
required int elapsedMs, required int elapsedMs,
required LinearCoordinates player, required Coordinate2D playerPosition,
required bool Function(int x, int y) isWalkable, required bool Function(int x, int y) isWalkable,
required void Function(int damage) onDamagePlayer, required void Function(int damage) onDamagePlayer,
}) { }) {
if (state == EntityState.idle) { if (state == EntityState.idle) {
if (hasLineOfSight(player, isWalkable)) { if (hasLineOfSight(playerPosition, isWalkable)) {
state = EntityState.patrolling; state = EntityState.patrolling;
lastActionTime = elapsedMs; lastActionTime = elapsedMs;
} }
@@ -65,8 +65,8 @@ class Dog extends Enemy {
state == EntityState.shooting) { state == EntityState.shooting) {
// "Shooting" here means biting // "Shooting" here means biting
double dx = player.x - x; double dx = playerPosition.x - x;
double dy = player.y - y; double dy = playerPosition.y - y;
double distance = math.sqrt(dx * dx + dy * dy); double distance = math.sqrt(dx * dx + dy * dy);
double angleToPlayer = math.atan2(dy, dx); double angleToPlayer = math.atan2(dy, dx);

View File

@@ -1,6 +1,6 @@
import 'dart:math' as math; 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'; import 'package:wolf_dart/features/entities/entity.dart';
abstract class Enemy extends Entity { abstract class Enemy extends Entity {
@@ -58,11 +58,11 @@ abstract class Enemy extends Entity {
// The enemy can now check its own line of sight! // The enemy can now check its own line of sight!
bool hasLineOfSight( bool hasLineOfSight(
LinearCoordinates player, Coordinate2D playerPosition,
bool Function(int x, int y) isWalkable, bool Function(int x, int y) isWalkable,
) { ) {
double dx = player.x - x; double dx = playerPosition.x - x;
double dy = player.y - y; double dy = playerPosition.y - y;
double distance = math.sqrt(dx * dx + dy * dy); double distance = math.sqrt(dx * dx + dy * dy);
// 1. FOV Check // 1. FOV Check
@@ -94,7 +94,7 @@ abstract class Enemy extends Entity {
// The weapon asks the enemy if it is unobstructed from the shooter // The weapon asks the enemy if it is unobstructed from the shooter
bool hasLineOfSightFrom( bool hasLineOfSightFrom(
LinearCoordinates source, Coordinate2D source,
double sourceAngle, double sourceAngle,
double distance, double distance,
bool Function(int x, int y) isWalkable, bool Function(int x, int y) isWalkable,
@@ -112,7 +112,7 @@ abstract class Enemy extends Entity {
void update({ void update({
required int elapsedMs, required int elapsedMs,
required LinearCoordinates player, required Coordinate2D playerPosition,
required bool Function(int x, int y) isWalkable, required bool Function(int x, int y) isWalkable,
required void Function(int damage) onDamagePlayer, required void Function(int damage) onDamagePlayer,
}); });

View File

@@ -1,6 +1,6 @@
import 'dart:math' as math; 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/collectible.dart';
import 'package:wolf_dart/features/entities/entity.dart'; import 'package:wolf_dart/features/entities/entity.dart';
import 'package:wolf_dart/features/weapon/weapon.dart'; import 'package:wolf_dart/features/weapon/weapon.dart';
@@ -55,7 +55,7 @@ class Player {
} }
// Helper getter to interface with the RaycasterPainter // Helper getter to interface with the RaycasterPainter
LinearCoordinates get position => (x: x, y: y); Coordinate2D get position => Coordinate2D(x, y);
// --- Weapon Switching & Animation Logic --- // --- Weapon Switching & Animation Logic ---

View File

@@ -2,7 +2,7 @@ import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.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/classes/matrix.dart';
import 'package:wolf_dart/features/difficulty/difficulty.dart'; import 'package:wolf_dart/features/difficulty/difficulty.dart';
import 'package:wolf_dart/features/entities/collectible.dart'; import 'package:wolf_dart/features/entities/collectible.dart';
@@ -146,7 +146,7 @@ class _WolfRendererState extends State<WolfRenderer>
pX >= currentLevel[0].length || pX >= currentLevel[0].length ||
currentLevel[pY][pX] > 0) { currentLevel[pY][pX] > 0) {
double shortestDist = double.infinity; 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 y = 0; y < currentLevel.length; y++) {
for (int x = 0; x < currentLevel[y].length; x++) { for (int x = 0; x < currentLevel[y].length; x++) {
@@ -159,7 +159,7 @@ class _WolfRendererState extends State<WolfRenderer>
if (dist < shortestDist) { if (dist < shortestDist) {
shortestDist = dist; shortestDist = dist;
nearestSafeSpot = (x: safeX, y: safeY); nearestSafeSpot = Coordinate2D(safeX, safeY);
} }
} }
} }
@@ -186,7 +186,7 @@ class _WolfRendererState extends State<WolfRenderer>
// 2. Explicit State Updates // 2. Explicit State Updates
player.updateWeaponSwitch(); player.updateWeaponSwitch();
_applyMovementAndCollision(movement.dx, movement.dy); _applyMovementAndCollision(movement.x, movement.y);
_updateEntities(elapsed); _updateEntities(elapsed);
// Explicit reassignment from a pure(r) function // Explicit reassignment from a pure(r) function
@@ -209,7 +209,7 @@ class _WolfRendererState extends State<WolfRenderer>
} }
// Returns movement deltas instead of modifying class variables // Returns movement deltas instead of modifying class variables
({double dx, double dy}) _processInputs(Duration elapsed) { Coordinate2D _processInputs(Duration elapsed) {
inputManager.update(); inputManager.update();
const double moveSpeed = 0.16; const double moveSpeed = 0.16;
@@ -248,7 +248,7 @@ class _WolfRendererState extends State<WolfRenderer>
); );
} }
return (dx: dx, dy: dy); return Coordinate2D(dx, dy);
} }
// Now receives dx and dy explicitly // Now receives dx and dy explicitly
@@ -277,7 +277,7 @@ class _WolfRendererState extends State<WolfRenderer>
if (entity is Enemy) { if (entity is Enemy) {
entity.update( entity.update(
elapsedMs: elapsed.inMilliseconds, elapsedMs: elapsed.inMilliseconds,
player: player.position, playerPosition: player.position,
isWalkable: _isWalkable, isWalkable: _isWalkable,
onDamagePlayer: _takeDamage, onDamagePlayer: _takeDamage,
); );

View File

@@ -1,6 +1,6 @@
import 'dart:math' as math; 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/enemies/enemy.dart';
import 'package:wolf_dart/features/entities/entity.dart'; import 'package:wolf_dart/features/entities/entity.dart';
@@ -83,7 +83,7 @@ abstract class Weapon {
double threshold = 0.2 / dist; double threshold = 0.2 / dist;
if (angleDiff.abs() < threshold) { 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 // Delegate to the enemy to check if it's visible
if (entity.hasLineOfSightFrom( if (entity.hasLineOfSightFrom(