Fixed ASCII rasterizer, abstracted out input and audio, and created CLI client (untested)

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-16 14:47:10 +01:00
parent 6f7885a924
commit ede2c3fa31
18 changed files with 353 additions and 166 deletions

96
bin/cli_main.dart Normal file
View File

@@ -0,0 +1,96 @@
import 'dart:async';
import 'dart:io';
import 'package:wolf_3d_data/wolf_3d_data.dart';
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
import 'package:wolf_3d_input/wolf_3d_input.dart';
import 'package:wolf_3d_synth/wolf_3d_synth.dart';
// Helper to gracefully exit and restore the terminal
void exitCleanly(int code) {
stdout.write('\x1b[0m'); // Reset color
stdout.write('\x1b[2J\x1b[H'); // Clear screen
stdout.write('\x1b[?25h'); // SHOW the cursor again
exit(code);
}
void main() async {
stdin.echoMode = false;
stdin.lineMode = false;
// HIDE the blinking cursor and clear the screen to prep the canvas
stdout.write('\x1b[?25l\x1b[2J');
// ... (Keep your game discovery and instantiation exactly the same) ...
final availableGames = await WolfensteinLoader.discover(
directoryPath: 'assets/shareware',
recursive: true,
);
final data = availableGames.values.first;
final input = CliInput();
final cliAudio = CliSilentAudio();
final engine = WolfEngine(
data: data,
difficulty: Difficulty.bringEmOn,
startingEpisode: 0,
audio: cliAudio,
onGameWon: () {
exitCleanly(0);
print("YOU WON!");
},
);
final rasterizer = AsciiRasterizer();
final buffer = FrameBuffer(120, 40);
engine.init();
stdin.listen((List<int> bytes) {
if (bytes.contains(113) || bytes.contains(27)) {
exitCleanly(0);
}
input.handleKey(bytes);
});
Stopwatch stopwatch = Stopwatch()..start();
Duration lastTick = Duration.zero;
Timer.periodic(const Duration(milliseconds: 33), (timer) {
// 1. Terminal Size Safety Check
if (stdout.hasTerminal) {
if (stdout.terminalColumns < 120 || stdout.terminalLines < 40) {
// Clear the screen and print the warning at the top left
stdout.write('\x1b[2J\x1b[H');
stdout.write('\x1b[31m[ ERROR ] TERMINAL TOO SMALL\x1b[0m\n\n');
stdout.write(
'Wolfenstein 3D requires a minimum resolution of 120x40.\n',
);
stdout.write(
'Current size: \x1b[33m${stdout.terminalColumns}x${stdout.terminalLines}\x1b[0m\n\n',
);
stdout.write('Please resize your window to resume the game...');
// Prevent the engine from simulating a massive time jump when resumed
lastTick = stopwatch.elapsed;
return;
}
}
// 2. Normal Game Loop
Duration currentTick = stopwatch.elapsed;
Duration elapsed = currentTick - lastTick;
lastTick = currentTick;
// Move cursor to top-left (0,0) before drawing the frame
stdout.write('\x1b[H');
engine.tick(elapsed, input.currentInput);
rasterizer.render(engine, buffer);
rasterizer.finalizeFrame();
stdout.write(rasterizer.toAnsiString());
});
}

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_flutter/wolf_3d.dart'; import 'package:wolf_3d_flutter/wolf_3d.dart';
import 'package:wolf_3d_renderer/wolf_3d_renderer.dart'; import 'package:wolf_3d_renderer/wolf_3d_ascii_renderer.dart';
class DifficultyScreen extends StatefulWidget { class DifficultyScreen extends StatefulWidget {
const DifficultyScreen({ const DifficultyScreen({
@@ -26,7 +26,7 @@ class _DifficultyScreenState extends State<DifficultyScreen> {
Navigator.of(context).pushReplacement( Navigator.of(context).pushReplacement(
MaterialPageRoute( MaterialPageRoute(
builder: (_) => WolfRenderer( builder: (_) => WolfAsciiRenderer(
Wolf3d.I.activeGame, Wolf3d.I.activeGame,
difficulty: difficulty, difficulty: difficulty,
startingEpisode: Wolf3d.I.activeEpisode, startingEpisode: Wolf3d.I.activeEpisode,

View File

@@ -1,6 +1,5 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_engine/wolf_3d_engine.dart'; import 'package:wolf_3d_engine/wolf_3d_engine.dart';
@@ -34,8 +33,17 @@ abstract class AsciiThemes {
class ColoredChar { class ColoredChar {
final String char; final String char;
final Color color; final int rawColor; // Stores the AABBGGRR integer from the palette
ColoredChar(this.char, this.color);
ColoredChar(this.char, this.rawColor);
// Safely extract the exact RGB channels regardless of framework
int get r => rawColor & 0xFF;
int get g => (rawColor >> 8) & 0xFF;
int get b => (rawColor >> 16) & 0xFF;
// Outputs standard AARRGGBB for Flutter's Color(int) constructor
int get argb => (0xFF000000) | (r << 16) | (g << 8) | b;
} }
class AsciiRasterizer extends Rasterizer { class AsciiRasterizer extends Rasterizer {
@@ -49,38 +57,29 @@ class AsciiRasterizer extends Rasterizer {
@override @override
double get aspectMultiplier => 0.6; double get aspectMultiplier => 0.6;
// --- HELPER: Color Conversion ---
Color _vgaToColor(int vgaColor) {
int r = vgaColor & 0xFF;
int g = (vgaColor >> 8) & 0xFF;
int b = (vgaColor >> 16) & 0xFF;
return Color.fromARGB(255, r, g, b);
}
// Intercept the base render call to initialize our text grid // Intercept the base render call to initialize our text grid
@override @override
dynamic render(WolfEngine engine, FrameBuffer buffer) { dynamic render(WolfEngine engine, FrameBuffer buffer) {
_engine = engine; _engine = engine;
_screen = List.generate( _screen = List.generate(
buffer.height, buffer.height,
(_) => List.filled(buffer.width, ColoredChar(' ', Colors.black)), (_) =>
List.filled(buffer.width, ColoredChar(' ', ColorPalette.vga32Bit[0])),
); );
return super.render(engine, buffer); return super.render(engine, buffer);
} }
@override @override
void prepareFrame(WolfEngine engine) { void prepareFrame(WolfEngine engine) {
final Color ceilingColor = _vgaToColor(ColorPalette.vga32Bit[25]); // Just grab the raw ints!
final Color floorColor = _vgaToColor(ColorPalette.vga32Bit[29]); final int ceilingColor = ColorPalette.vga32Bit[25];
final int floorColor = ColorPalette.vga32Bit[29];
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
if (y < viewHeight / 2) { if (y < viewHeight / 2) {
// Fetch the solid character from the theme
_screen[y][x] = ColoredChar(activeTheme.solid, ceilingColor); _screen[y][x] = ColoredChar(activeTheme.solid, ceilingColor);
} else if (y < viewHeight) { } else if (y < viewHeight) {
// Fetch the solid character from the theme
_screen[y][x] = ColoredChar(activeTheme.solid, floorColor); _screen[y][x] = ColoredChar(activeTheme.solid, floorColor);
} }
} }
@@ -98,7 +97,7 @@ class AsciiRasterizer extends Rasterizer {
double perpWallDist, double perpWallDist,
int side, int side,
) { ) {
double brightness = (4.0 / (perpWallDist + 1.0)); double brightness = calculateDepthBrightness(perpWallDist);
String wallChar = activeTheme.getByBrightness(brightness); String wallChar = activeTheme.getByBrightness(brightness);
for (int y = drawStart; y < drawEnd; y++) { for (int y = drawStart; y < drawEnd; y++) {
@@ -107,16 +106,11 @@ class AsciiRasterizer extends Rasterizer {
int texY = (relativeY * 64).toInt().clamp(0, 63); int texY = (relativeY * 64).toInt().clamp(0, 63);
int colorByte = texture.pixels[texX * 64 + texY]; int colorByte = texture.pixels[texX * 64 + texY];
Color pixelColor = _vgaToColor(ColorPalette.vga32Bit[colorByte]); int pixelColor = ColorPalette.vga32Bit[colorByte]; // Raw int
// Faux directional lighting // Faux directional lighting using your new base class method
if (side == 1) { if (side == 1) {
pixelColor = Color.fromARGB( pixelColor = shadeColor(pixelColor);
255,
(pixelColor.r * 0.9).toInt().clamp(0, 255),
(pixelColor.g * 0.9).toInt().clamp(0, 255),
(pixelColor.b * 0.9).toInt().clamp(0, 255),
);
} }
_screen[y][x] = ColoredChar(wallChar, pixelColor); _screen[y][x] = ColoredChar(wallChar, pixelColor);
@@ -133,7 +127,8 @@ class AsciiRasterizer extends Rasterizer {
int texX, int texX,
double transformY, double transformY,
) { ) {
double brightness = (4.0 / (transformY + 1.0)).clamp(0.0, 1.0); // Ask the base class for the depth brightness
double brightness = calculateDepthBrightness(transformY);
String spriteChar = activeTheme.getByBrightness(brightness); String spriteChar = activeTheme.getByBrightness(brightness);
for ( for (
@@ -148,7 +143,7 @@ class AsciiRasterizer extends Rasterizer {
if (colorByte != 255) { if (colorByte != 255) {
_screen[y][stripeX] = ColoredChar( _screen[y][stripeX] = ColoredChar(
spriteChar, spriteChar,
_vgaToColor(ColorPalette.vga32Bit[colorByte]), ColorPalette.vga32Bit[colorByte],
); );
} }
} }
@@ -180,7 +175,7 @@ class AsciiRasterizer extends Rasterizer {
if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < viewHeight) { if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < viewHeight) {
_screen[drawY][drawX] = ColoredChar( _screen[drawY][drawX] = ColoredChar(
activeTheme.solid, activeTheme.solid,
_vgaToColor(ColorPalette.vga32Bit[colorByte]), ColorPalette.vga32Bit[colorByte],
); );
} }
} }
@@ -310,10 +305,9 @@ class AsciiRasterizer extends Rasterizer {
int colorByte = image.pixels[index]; int colorByte = image.pixels[index];
if (colorByte != 255) { if (colorByte != 255) {
// Using '' for UI to make it look solid
_screen[drawY][drawX] = ColoredChar( _screen[drawY][drawX] = ColoredChar(
activeTheme.solid, activeTheme.solid,
_vgaToColor(ColorPalette.vga32Bit[colorByte]), ColorPalette.vga32Bit[colorByte],
); );
} }
} }
@@ -322,7 +316,6 @@ class AsciiRasterizer extends Rasterizer {
} }
// --- DAMAGE FLASH --- // --- DAMAGE FLASH ---
void _applyDamageFlash() { void _applyDamageFlash() {
double intensity = _engine.player.damageFlash; double intensity = _engine.player.damageFlash;
int redBoost = (150 * intensity).toInt(); int redBoost = (150 * intensity).toInt();
@@ -330,24 +323,55 @@ class AsciiRasterizer extends Rasterizer {
for (int y = 0; y < viewHeight; y++) { for (int y = 0; y < viewHeight; y++) {
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
Color c = _screen[y][x].color; ColoredChar cell = _screen[y][x];
int r = ((c.r * 255).round().clamp(0, 255) + redBoost).clamp(0, 255); // Use our safe getters!
int g = ((c.g * 255).round().clamp(0, 255) * colorDrop).toInt().clamp( int r = cell.r;
0, int g = cell.g;
255, int b = cell.b;
);
int b = ((c.b * 255).round().clamp(0, 255) * colorDrop).toInt().clamp(
0,
255,
);
// Replace the existing character with a red-tinted version r = (r + redBoost).clamp(0, 255);
_screen[y][x] = ColoredChar( g = (g * colorDrop).toInt().clamp(0, 255);
_screen[y][x].char, b = (b * colorDrop).toInt().clamp(0, 255);
Color.fromARGB(255, r, g, b),
); // Pack back into the native AABBGGRR format that ColoredChar expects
int newRawColor = (0xFF000000) | (b << 16) | (g << 8) | r;
_screen[y][x] = ColoredChar(cell.char, newRawColor);
} }
} }
} }
/// Converts the current frame to a single printable ANSI string
String toAnsiString() {
StringBuffer buffer = StringBuffer();
int lastR = -1;
int lastG = -1;
int lastB = -1;
for (int y = 0; y < _screen.length; y++) {
List<ColoredChar> row = _screen[y];
for (ColoredChar cell in row) {
if (cell.r != lastR || cell.g != lastG || cell.b != lastB) {
buffer.write('\x1b[38;2;${cell.r};${cell.g};${cell.b}m');
lastR = cell.r;
lastG = cell.g;
lastB = cell.b;
}
buffer.write(cell.char);
}
// Only print a newline if we are NOT on the very last row.
// This stops the terminal from scrolling down!
if (y < _screen.length - 1) {
buffer.write('\n');
}
}
// Reset the terminal color at the very end
buffer.write('\x1b[0m');
return buffer.toString();
}
} }

View File

@@ -80,6 +80,17 @@ abstract class Rasterizer {
/// Return the finished frame (e.g., the FrameBuffer itself, or an ASCII list). /// Return the finished frame (e.g., the FrameBuffer itself, or an ASCII list).
dynamic finalizeFrame(); dynamic finalizeFrame();
// ===========================================================================
// SHARED LIGHTING MATH
// ===========================================================================
/// Calculates depth-based lighting falloff (0.0 to 1.0).
/// While the original Wolf3D didn't use depth fog, this provides a great
/// atmospheric effect for custom renderers (like ASCII dithering).
double calculateDepthBrightness(double distance) {
return (10.0 / (distance + 2.0)).clamp(0.0, 1.0);
}
// =========================================================================== // ===========================================================================
// CORE ENGINE MATH (Shared across all renderers) // CORE ENGINE MATH (Shared across all renderers)
// =========================================================================== // ===========================================================================
@@ -359,4 +370,12 @@ abstract class Rasterizer {
} }
} }
} }
/// Darkens a 32-bit 0xAABBGGRR color by roughly 30% without touching Alpha
int shadeColor(int color) {
int r = (color & 0xFF) * 7 ~/ 10;
int g = ((color >> 8) & 0xFF) * 7 ~/ 10;
int b = ((color >> 16) & 0xFF) * 7 ~/ 10;
return (0xFF000000) | (b << 16) | (g << 8) | r;
}
} }

View File

@@ -51,7 +51,7 @@ class SoftwareRasterizer extends Rasterizer {
// Darken Y-side walls for faux directional lighting // Darken Y-side walls for faux directional lighting
if (side == 1) { if (side == 1) {
pixelColor = _shadeColor(pixelColor); pixelColor = shadeColor(pixelColor);
} }
_buffer.pixels[y * width + x] = pixelColor; _buffer.pixels[y * width + x] = pixelColor;
@@ -235,14 +235,6 @@ class SoftwareRasterizer extends Rasterizer {
} }
} }
/// Darkens a 32-bit 0xAABBGGRR color by roughly 30% without touching Alpha
int _shadeColor(int color) {
int r = (color & 0xFF) * 7 ~/ 10;
int g = ((color >> 8) & 0xFF) * 7 ~/ 10;
int b = ((color >> 16) & 0xFF) * 7 ~/ 10;
return (0xFF000000) | (b << 16) | (g << 8) | r;
}
/// Tints the top 80% of the screen red based on player.damageFlash intensity /// Tints the top 80% of the screen red based on player.damageFlash intensity
void _applyDamageFlash() { void _applyDamageFlash() {
// Grab the intensity (0.0 to 1.0) // Grab the intensity (0.0 to 1.0)

View File

@@ -3,6 +3,7 @@
/// More dartdocs go here. /// More dartdocs go here.
library; library;
export 'src/ascii_rasterizer.dart';
export 'src/engine_audio.dart'; export 'src/engine_audio.dart';
export 'src/engine_input.dart'; export 'src/engine_input.dart';
export 'src/managers/door_manager.dart'; export 'src/managers/door_manager.dart';

View File

@@ -0,0 +1,38 @@
import 'package:flutter/services.dart';
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
import 'package:wolf_3d_input/wolf_3d_input.dart';
class Wolf3dFlutterInput extends Wolf3dInput {
Set<LogicalKeyboardKey> _previousKeys = {};
@override
void update() {
final pressedKeys = HardwareKeyboard.instance.logicalKeysPressed;
final newlyPressedKeys = pressedKeys.difference(_previousKeys);
isMovingForward = pressedKeys.contains(LogicalKeyboardKey.keyW);
isMovingBackward = pressedKeys.contains(LogicalKeyboardKey.keyS);
isTurningLeft = pressedKeys.contains(LogicalKeyboardKey.keyA);
isTurningRight = pressedKeys.contains(LogicalKeyboardKey.keyD);
isInteracting = newlyPressedKeys.contains(LogicalKeyboardKey.space);
isFiring =
pressedKeys.contains(LogicalKeyboardKey.controlLeft) &&
!pressedKeys.contains(LogicalKeyboardKey.space);
requestedWeapon = null;
for (final LogicalKeyboardKey key in newlyPressedKeys) {
if (key == LogicalKeyboardKey.digit1) requestedWeapon = WeaponType.knife;
if (key == LogicalKeyboardKey.digit2) requestedWeapon = WeaponType.pistol;
if (key == LogicalKeyboardKey.digit3) {
requestedWeapon = WeaponType.machineGun;
}
if (key == LogicalKeyboardKey.digit4) {
requestedWeapon = WeaponType.chainGun;
}
}
_previousKeys = Set.from(pressedKeys);
}
}

View File

@@ -16,6 +16,8 @@ dependencies:
wolf_3d_data_types: any wolf_3d_data_types: any
wolf_3d_synth: any wolf_3d_synth: any
wolf_3d_engine: any wolf_3d_engine: any
wolf_3d_entities: any
wolf_3d_input: any
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -0,0 +1,47 @@
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
import 'package:wolf_3d_input/src/wolf_3d_input.dart';
class CliInput extends Wolf3dInput {
// Pending buffer for asynchronous stdin events
bool _pForward = false;
bool _pBackward = false;
bool _pLeft = false;
bool _pRight = false;
bool _pFire = false;
bool _pInteract = false;
WeaponType? _pWeapon;
/// Call this directly from the stdin listener to queue inputs for the next frame
void handleKey(List<int> bytes) {
String char = String.fromCharCodes(bytes).toLowerCase();
if (char == 'w') _pForward = true;
if (char == 's') _pBackward = true;
if (char == 'a') _pLeft = true;
if (char == 'd') _pRight = true;
if (char == 'f' || char == ' ') _pFire = true;
if (char == 'e') _pInteract = true;
if (char == '1') _pWeapon = WeaponType.knife;
if (char == '2') _pWeapon = WeaponType.pistol;
if (char == '3') _pWeapon = WeaponType.machineGun;
if (char == '4') _pWeapon = WeaponType.chainGun;
}
@override
void update() {
// 1. Move pending inputs to the active state
isMovingForward = _pForward;
isMovingBackward = _pBackward;
isTurningLeft = _pLeft;
isTurningRight = _pRight;
isFiring = _pFire;
isInteracting = _pInteract;
requestedWeapon = _pWeapon;
// 2. Wipe the pending slate clean for the next frame
_pForward = _pBackward = _pLeft = _pRight = _pFire = _pInteract = false;
_pWeapon = null;
}
}

View File

@@ -0,0 +1,27 @@
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
abstract class Wolf3dInput {
bool isMovingForward = false;
bool isMovingBackward = false;
bool isTurningLeft = false;
bool isTurningRight = false;
bool isInteracting = false;
bool isFiring = false;
WeaponType? requestedWeapon;
/// Called once per frame by the game loop to refresh the state.
void update();
/// Exports the current state as a clean DTO for the engine.
/// Subclasses do not need to override this.
EngineInput get currentInput => EngineInput(
isMovingForward: isMovingForward,
isMovingBackward: isMovingBackward,
isTurningLeft: isTurningLeft,
isTurningRight: isTurningRight,
isFiring: isFiring,
isInteracting: isInteracting,
requestedWeapon: requestedWeapon,
);
}

View File

@@ -1,58 +1,4 @@
import 'package:flutter/services.dart'; library;
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
class WolfInput { export 'src/cli_input.dart';
Set<LogicalKeyboardKey> _previousKeys = {}; export 'src/wolf_3d_input.dart';
bool isMovingForward = false;
bool isMovingBackward = false;
bool isTurningLeft = false;
bool isTurningRight = false;
bool isInteracting = false;
bool isFiring = false;
WeaponType? requestedWeapon;
void update() {
final pressedKeys = HardwareKeyboard.instance.logicalKeysPressed;
final newlyPressedKeys = pressedKeys.difference(_previousKeys);
isMovingForward = pressedKeys.contains(LogicalKeyboardKey.keyW);
isMovingBackward = pressedKeys.contains(LogicalKeyboardKey.keyS);
isTurningLeft = pressedKeys.contains(LogicalKeyboardKey.keyA);
isTurningRight = pressedKeys.contains(LogicalKeyboardKey.keyD);
isInteracting = newlyPressedKeys.contains(LogicalKeyboardKey.space);
isFiring =
pressedKeys.contains(LogicalKeyboardKey.controlLeft) &&
!pressedKeys.contains(LogicalKeyboardKey.space);
requestedWeapon = null;
for (final LogicalKeyboardKey key in newlyPressedKeys) {
switch (key) {
case LogicalKeyboardKey.digit1:
requestedWeapon = WeaponType.knife;
case LogicalKeyboardKey.digit2:
requestedWeapon = WeaponType.pistol;
case LogicalKeyboardKey.digit3:
requestedWeapon = WeaponType.machineGun;
case LogicalKeyboardKey.digit4:
requestedWeapon = WeaponType.chainGun;
}
}
_previousKeys = Set.from(pressedKeys);
}
/// Exports the current state as a clean DTO for the engine
EngineInput get currentInput => EngineInput(
isMovingForward: isMovingForward,
isMovingBackward: isMovingBackward,
isTurningLeft: isTurningLeft,
isTurningRight: isTurningRight,
isFiring: isFiring,
isInteracting: isInteracting,
requestedWeapon: requestedWeapon,
);
}

View File

@@ -5,54 +5,9 @@ homepage:
environment: environment:
sdk: ^3.11.1 sdk: ^3.11.1
flutter: ">=1.17.0"
resolution: workspace resolution: workspace
dependencies: dependencies:
flutter:
sdk: flutter
wolf_3d_entities: any wolf_3d_entities: any
wolf_3d_engine: any wolf_3d_engine: any
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/to/asset-from-package
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/to/font-from-package

View File

@@ -2,3 +2,6 @@ include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at # Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options # https://dart.dev/guides/language/analysis-options
formatter:
trailing_commas: preserve

View File

@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_engine/wolf_3d_engine.dart'; import 'package:wolf_3d_engine/wolf_3d_engine.dart';
import 'package:wolf_3d_flutter/wolf_3d_input_flutter.dart';
import 'package:wolf_3d_input/wolf_3d_input.dart'; import 'package:wolf_3d_input/wolf_3d_input.dart';
import 'package:wolf_3d_renderer/ascii_rasterizer.dart';
class WolfAsciiRenderer extends StatefulWidget { class WolfAsciiRenderer extends StatefulWidget {
const WolfAsciiRenderer( const WolfAsciiRenderer(
@@ -25,7 +25,7 @@ class WolfAsciiRenderer extends StatefulWidget {
class _WolfAsciiRendererState extends State<WolfAsciiRenderer> class _WolfAsciiRendererState extends State<WolfAsciiRenderer>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
final WolfInput inputManager = WolfInput(); final Wolf3dInput inputManager = Wolf3dFlutterInput();
late final WolfEngine engine; late final WolfEngine engine;
late Ticker _gameLoop; late Ticker _gameLoop;
final FocusNode _focusNode = FocusNode(); final FocusNode _focusNode = FocusNode();
@@ -116,11 +116,11 @@ class AsciiFrameWidget extends StatelessWidget {
children: frameData.map((row) { children: frameData.map((row) {
List<TextSpan> optimizedSpans = []; List<TextSpan> optimizedSpans = [];
if (row.isNotEmpty) { if (row.isNotEmpty) {
Color currentColor = row[0].color; Color currentColor = Color(row[0].argb);
StringBuffer currentSegment = StringBuffer(row[0].char); StringBuffer currentSegment = StringBuffer(row[0].char);
for (int i = 1; i < row.length; i++) { for (int i = 1; i < row.length; i++) {
if (row[i].color == currentColor) { if (Color(row[i].argb) == currentColor) {
currentSegment.write(row[i].char); currentSegment.write(row[i].char);
} else { } else {
optimizedSpans.add( optimizedSpans.add(
@@ -129,7 +129,7 @@ class AsciiFrameWidget extends StatelessWidget {
style: TextStyle(color: currentColor), style: TextStyle(color: currentColor),
), ),
); );
currentColor = row[i].color; currentColor = Color(row[i].argb);
currentSegment = StringBuffer(row[i].char); currentSegment = StringBuffer(row[i].char);
} }
} }

View File

@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_engine/wolf_3d_engine.dart'; import 'package:wolf_3d_engine/wolf_3d_engine.dart';
import 'package:wolf_3d_flutter/wolf_3d_input_flutter.dart';
import 'package:wolf_3d_input/wolf_3d_input.dart'; import 'package:wolf_3d_input/wolf_3d_input.dart';
class WolfRenderer extends StatefulWidget { class WolfRenderer extends StatefulWidget {
@@ -26,7 +27,7 @@ class WolfRenderer extends StatefulWidget {
class _WolfRendererState extends State<WolfRenderer> class _WolfRendererState extends State<WolfRenderer>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
final WolfInput inputManager = WolfInput(); final Wolf3dInput inputManager = Wolf3dFlutterInput();
late final WolfEngine engine; late final WolfEngine engine;
late Ticker _gameLoop; late Ticker _gameLoop;
final FocusNode _focusNode = FocusNode(); final FocusNode _focusNode = FocusNode();

View File

@@ -16,6 +16,7 @@ dependencies:
wolf_3d_engine: any wolf_3d_engine: any
wolf_3d_entities: any wolf_3d_entities: any
wolf_3d_input: any wolf_3d_input: any
wolf_3d_flutter: any
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -0,0 +1,34 @@
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
class CliSilentAudio implements EngineAudio {
@override
WolfensteinData? activeGame;
@override
Future<void> init() async {
// No-op for CLI
}
@override
void playMenuMusic() {}
@override
void playLevelMusic(WolfLevel level) {
// Optional: Print a log so you know it's working!
// print("🎵 Playing music for: ${level.name} 🎵");
}
@override
void stopMusic() {}
@override
void playSoundEffect(int sfxId) {
// Optional: You could use the terminal 'bell' character here
// to actually make a system beep when a sound plays!
// stdout.write('\x07');
}
@override
void dispose() {}
}

View File

@@ -3,4 +3,5 @@
/// More dartdocs go here. /// More dartdocs go here.
library; library;
export 'src/silent_renderer.dart' show CliSilentAudio;
export 'src/wolf_3d_audio.dart' show WolfAudio; export 'src/wolf_3d_audio.dart' show WolfAudio;