import 'dart:async'; import 'dart:math' as math; import 'package:flutter/material.dart'; void main() { runApp(const StarTaxiApp()); } class StarTaxiApp extends StatelessWidget { const StarTaxiApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Star Taxi – APK Test', theme: ThemeData.dark(useMaterial3: true), home: const StarTaxiDemo(), ); } } class StarTaxiDemo extends StatefulWidget { const StarTaxiDemo({super.key}); @override State createState() => _StarTaxiDemoState(); } class _StarTaxiDemoState extends State { static const int lanes = 3; static const double playerY = 0.86; final _rng = math.Random(); final List<_Obstacle> _obstacles = []; Timer? _loop; double _lane = 1; double _speed = 0.010; int _score = 0; int _best = 0; bool _running = true; @override void initState() { super.initState(); _spawnInitial(); _loop = Timer.periodic(const Duration(milliseconds: 16), (_) => _tick()); } @override void dispose() { _loop?.cancel(); super.dispose(); } void _spawnInitial() { _obstacles.clear(); for (var i = 0; i < 5; i++) { _obstacles.add(_Obstacle( lane: _rng.nextInt(lanes).toDouble(), y: -0.8 - i * 0.45, )); } } void _tick() { if (!_running) return; setState(() { _speed = (_speed + 0.00001).clamp(0.010, 0.03); _score += 1; for (final o in _obstacles) { o.y += _speed; if (o.y > 1.15) { o.y = -0.9 - _rng.nextDouble() * 0.8; o.lane = _rng.nextInt(lanes).toDouble(); } } for (final o in _obstacles) { if ((o.y - playerY).abs() < 0.085 && (o.lane - _lane).abs() < 0.1) { _running = false; _best = math.max(_best, _score); break; } } }); } void _move(int dir) { if (!_running) return; setState(() { _lane = (_lane + dir).clamp(0, lanes - 1).toDouble(); }); } void _restart() { setState(() { _score = 0; _speed = 0.010; _lane = 1; _running = true; _spawnInitial(); }); } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Column( children: [ Padding( padding: const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Score: $_score', style: const TextStyle(fontSize: 20)), Text('Best: $_best', style: const TextStyle(fontSize: 18)), ], ), ), Expanded( child: AspectRatio( aspectRatio: 9 / 16, child: Container( margin: const EdgeInsets.all(10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), gradient: const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0xFF0B1021), Color(0xFF1B2348)], ), ), child: CustomPaint( painter: _RoadPainter( lane: _lane, obstacles: _obstacles, ), child: !_running ? Center( child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.65), borderRadius: BorderRadius.circular(12), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text('Crash! 💥', style: TextStyle(fontSize: 26)), const SizedBox(height: 8), ElevatedButton( onPressed: _restart, child: const Text('Nochmal'), ), ], ), ), ) : null, ), ), ), ), Padding( padding: const EdgeInsets.fromLTRB(20, 8, 20, 20), child: Row( children: [ Expanded( child: FilledButton.icon( onPressed: () => _move(-1), icon: const Icon(Icons.arrow_left), label: const Text('Links'), ), ), const SizedBox(width: 12), Expanded( child: FilledButton.icon( onPressed: () => _move(1), icon: const Icon(Icons.arrow_right), label: const Text('Rechts'), ), ), ], ), ), const Padding( padding: EdgeInsets.only(bottom: 10), child: Text('Star Taxi – kleines 3D-Arcade APK Testbuild'), ), ], ), ), ); } } class _RoadPainter extends CustomPainter { final double lane; final List<_Obstacle> obstacles; const _RoadPainter({required this.lane, required this.obstacles}); @override void paint(Canvas canvas, Size size) { final road = Paint()..color = const Color(0xFF2A2F40); final line = Paint() ..color = const Color(0xAAFFFFFF) ..strokeWidth = 2; final topWidth = size.width * 0.35; final bottomWidth = size.width * 0.95; final roadPath = Path() ..moveTo((size.width - topWidth) / 2, 0) ..lineTo((size.width + topWidth) / 2, 0) ..lineTo((size.width + bottomWidth) / 2, size.height) ..lineTo((size.width - bottomWidth) / 2, size.height) ..close(); canvas.drawPath(roadPath, road); for (int i = 1; i < 3; i++) { final t = i / 3; final xTop = size.width / 2 + (t - 0.5) * topWidth; final xBottom = size.width / 2 + (t - 0.5) * bottomWidth; canvas.drawLine(Offset(xTop, 0), Offset(xBottom, size.height), line); } for (double y = 0; y < 1.1; y += 0.12) { final py = y * size.height; final w = _roadWidthAtY(size, py, topWidth, bottomWidth); canvas.drawLine( Offset(size.width / 2 - w * 0.04, py), Offset(size.width / 2 + w * 0.04, py), Paint() ..color = Colors.white.withValues(alpha: 0.35) ..strokeWidth = 2, ); } for (final o in obstacles) { _drawCar( canvas, size, lane: o.lane, yNorm: o.y, color: const Color(0xFFE26D5A), ); } _drawCar( canvas, size, lane: lane, yNorm: _StarTaxiDemoState.playerY, color: const Color(0xFF3DDC97), ); } void _drawCar(Canvas canvas, Size size, {required double lane, required double yNorm, required Color color}) { final y = yNorm * size.height; final wRoad = _roadWidthAtY(size, y, size.width * 0.35, size.width * 0.95); final laneWidth = wRoad / 3; final left = size.width / 2 - wRoad / 2 + lane * laneWidth + laneWidth * 0.15; final width = laneWidth * 0.7; final height = width * 0.9; final rect = RRect.fromRectAndRadius( Rect.fromLTWH(left, y - height / 2, width, height), const Radius.circular(8), ); canvas.drawRRect(rect, Paint()..color = color); canvas.drawRRect( rect, Paint() ..style = PaintingStyle.stroke ..strokeWidth = 1.2 ..color = Colors.white.withValues(alpha: 0.65), ); } double _roadWidthAtY(Size size, double y, double topWidth, double bottomWidth) { final t = (y / size.height).clamp(0.0, 1.0); return topWidth + (bottomWidth - topWidth) * t; } @override bool shouldRepaint(covariant _RoadPainter oldDelegate) => true; } class _Obstacle { _Obstacle({required this.lane, required this.y}); double lane; double y; }