Skip to main content

beginner_tutorial/
beginner_tutorial.rs

1//! Beginner Tutorial: Building Your First Tile-Based Game
2
3#![allow(clippy::needless_return)]
4#![allow(clippy::implicit_return)]
5#![allow(clippy::uninlined_format_args)]
6#![allow(clippy::items_after_statements)]
7#![allow(clippy::unnecessary_cast)]
8#![allow(clippy::doc_markdown)]
9#![allow(clippy::cast_sign_loss)]
10#![allow(clippy::explicit_iter_loop)]
11#![allow(clippy::format_in_format_args)]
12#![allow(clippy::cast_precision_loss)]
13//! 
14//! This tutorial walks you through creating a simple tile-based game using tiles_tools.
15//! We'll build a basic dungeon explorer where a player moves around a grid, collects
16//! items, and encounters enemies. This demonstrates the core concepts and systems
17//! in an approachable way.
18//!
19//! # What You'll Learn
20//! 
21//! - Working with coordinate systems and tile grids
22//! - Basic pathfinding and movement
23//! - Entity management with ECS components  
24//! - Simple game state and turn management
25//! - Visual debugging and grid rendering
26//! - Event handling between game systems
27
28use tiles_tools::{
29    coordinates::{square::{Coordinate as SquareCoord, FourConnected}, Distance, Neighbors},
30    pathfind::astar,
31    ecs::{World, Position, Health},
32    debug::{GridRenderer, GridStyle, DebugColor},
33    game_systems::{TurnBasedGame, ResourceManager, GameStateMachine, GameState, GameStateEvent},
34    events::{EventBus, EventResult},
35};
36
37fn main() {
38    println!("šŸŽ® Tiles Tools Beginner Tutorial");
39    println!("=================================");
40    println!("Welcome to your first tile-based game!");
41    println!("We'll build a simple dungeon explorer step by step.\n");
42
43    // Step 1: Understanding Coordinates
44    tutorial_step_1_coordinates();
45    
46    // Step 2: Creating a Simple Map
47    tutorial_step_2_map_creation();
48    
49    // Step 3: Adding a Player
50    tutorial_step_3_player_basics();
51    
52    // Step 4: Movement and Pathfinding
53    tutorial_step_4_movement();
54    
55    // Step 5: Adding Game Entities
56    tutorial_step_5_entities();
57    
58    // Step 6: Simple Combat System
59    tutorial_step_6_combat();
60    
61    // Step 7: Visual Debugging
62    tutorial_step_7_debugging();
63    
64    // Step 8: Complete Game Example
65    tutorial_step_8_complete_game();
66
67    println!("\nšŸŽ‰ Tutorial Complete!");
68    println!("You've learned the fundamentals of tile-based game development with tiles_tools!");
69    println!("\nNext steps:");
70    println!("• Check out the other examples for advanced features");
71    println!("• Experiment with different coordinate systems (hexagonal, triangular)");
72    println!("• Try the animation system for smooth movement");
73    println!("• Explore the behavior tree system for advanced AI");
74    println!("• Add serialization for save/load functionality");
75}
76
77// Step 1: Understanding coordinate systems
78fn tutorial_step_1_coordinates() {
79    println!("šŸ“ Step 1: Understanding Coordinates");
80    println!("------------------------------------");
81    println!("Tiles_tools supports multiple coordinate systems. Let's start with square tiles.");
82    
83    // Create some coordinates
84    let start = SquareCoord::<FourConnected>::new(0, 0);
85    let destination = SquareCoord::<FourConnected>::new(3, 2);
86    
87    println!("Starting position: ({}, {})", start.x, start.y);
88    println!("Destination: ({}, {})", destination.x, destination.y);
89    
90    // Calculate distance
91    let distance = start.distance(&destination);
92    println!("Manhattan distance: {}", distance);
93    
94    // Get neighbors (4-connected square grid)
95    let neighbors = start.neighbors();
96    println!("Neighbors of start position:");
97    for (i, neighbor) in neighbors.iter().enumerate() {
98        println!("  {}: ({}, {})", i + 1, neighbor.x, neighbor.y);
99    }
100    
101    println!("āœ… Coordinates are the foundation of tile-based games!\n");
102}
103
104// Step 2: Creating a simple map with obstacles
105fn tutorial_step_2_map_creation() {
106    println!("šŸ—ŗļø Step 2: Creating a Simple Map");
107    println!("--------------------------------");
108    
109    // Define our dungeon map (true = walkable, false = wall)
110    let dungeon_map = [
111        [false, false, false, false, false, false, false, false],
112        [false, true,  true,  true,  false, true,  true,  false],
113        [false, true,  false, true,  false, true,  false, false],
114        [false, true,  true,  true,  true,  true,  true,  false],
115        [false, false, false, true,  false, false, true,  false],
116        [false, true,  true,  true,  false, true,  true,  false],
117        [false, false, false, false, false, false, false, false],
118    ];
119    
120    println!("Created a {}x{} dungeon map:", dungeon_map[0].len(), dungeon_map.len());
121    println!("Legend: # = wall, . = floor");
122    
123    // Display the map in a simple ASCII format
124    for row in &dungeon_map {
125        print!("  ");
126        for &cell in row {
127            print!("{}", if cell { "." } else { "#" });
128        }
129        println!();
130    }
131    
132    // Helper function to check if a coordinate is walkable
133    let is_walkable = |coord: &SquareCoord<FourConnected>| -> bool {
134        let x = coord.x as usize;
135        let y = coord.y as usize;
136        if y >= dungeon_map.len() || x >= dungeon_map[0].len() {
137            return false;
138        }
139        dungeon_map[y][x]
140    };
141    
142    // Test some positions
143    let test_positions = [
144        SquareCoord::<FourConnected>::new(1, 1), // Should be walkable
145        SquareCoord::<FourConnected>::new(0, 0), // Should be wall
146        SquareCoord::<FourConnected>::new(3, 3), // Should be walkable
147    ];
148    
149    println!("\nTesting walkability:");
150    for pos in &test_positions {
151        println!("  ({}, {}): {}", pos.x, pos.y, 
152                if is_walkable(pos) { "walkable" } else { "blocked" });
153    }
154    
155    println!("āœ… Map creation is essential for defining your game world!\n");
156}
157
158// Step 3: Adding a player character
159fn tutorial_step_3_player_basics() {
160    println!("šŸ§™ Step 3: Adding a Player Character");
161    println!("------------------------------------");
162    
163    // Create ECS world for managing entities
164    let mut world = World::new();
165    
166    // Create the player entity
167    let player_position = Position::new(SquareCoord::<FourConnected>::new(1, 1));
168    let player_health = Health::new(100);
169    
170    let player = world.spawn((player_position, player_health));
171    println!("Created player entity with ID: {:?}", player);
172    
173    // Query the player's data
174    for (entity, (pos, health)) in world.query::<(&Position<SquareCoord<FourConnected>>, &Health)>().iter() {
175        if entity == player {
176            println!("Player stats:");
177            println!("  Position: ({}, {})", pos.coord.x, pos.coord.y);
178            println!("  Health: {}/{}", health.current, health.maximum);
179        }
180    }
181    
182    // Demonstrate updating the player's position
183    // In a real game, you'd use proper ECS systems for this
184    println!("Player moved to (2, 1) - position updates would be handled by game systems");
185    
186    println!("āœ… Entities are the building blocks of your game objects!\n");
187}
188
189// Step 4: Basic movement and pathfinding
190fn tutorial_step_4_movement() {
191    println!("🚶 Step 4: Movement and Pathfinding");
192    println!("-----------------------------------");
193    
194    // Set up a simple obstacle map for pathfinding
195    let obstacles: Vec<SquareCoord<FourConnected>> = vec![
196        SquareCoord::new(2, 1),
197        SquareCoord::new(2, 2),
198        SquareCoord::new(2, 3),
199        SquareCoord::new(4, 2),
200        SquareCoord::new(4, 3),
201    ];
202    
203    // Define start and goal positions
204    let start = SquareCoord::<FourConnected>::new(1, 1);
205    let goal = SquareCoord::<FourConnected>::new(6, 3);
206    
207    println!("Finding path from ({}, {}) to ({}, {})", start.x, start.y, goal.x, goal.y);
208    println!("Obstacles at: {:?}", obstacles);
209    
210    // Use A* pathfinding to find the route
211    let path_result = astar(
212        &start,
213        &goal,
214        |coord| !obstacles.contains(coord), // Accessibility function
215        |_coord| 1, // Uniform cost function
216    );
217    
218    match path_result {
219        Some((path, cost)) => {
220            println!("āœ… Path found! {} steps with cost {}:", path.len(), cost);
221            for (i, step) in path.iter().enumerate() {
222                println!("  Step {}: ({}, {})", i, step.x, step.y);
223            }
224            
225            // Demonstrate simple movement validation
226            println!("\nValidating movement:");
227            for i in 1..path.len().min(4) { // Show first few moves
228                let from = &path[i-1];
229                let to = &path[i];
230                let is_valid = from.distance(to) == 1; // Adjacent squares only
231                println!("  {} -> {}: {}", 
232                    format!("({}, {})", from.x, from.y),
233                    format!("({}, {})", to.x, to.y),
234                    if is_valid { "valid" } else { "invalid" });
235            }
236        },
237        None => {
238            println!("āŒ No path found to destination!");
239        }
240    }
241    
242    println!("āœ… Pathfinding enables intelligent movement in your game!\n");
243}
244
245// Step 5: Adding different types of game entities
246fn tutorial_step_5_entities() {
247    println!("šŸ‘¾ Step 5: Adding Game Entities");
248    println!("-------------------------------");
249    
250    let mut world = World::new();
251    
252    // Create different types of entities
253    
254    // Player
255    let player = world.spawn((
256        Position::new(SquareCoord::<FourConnected>::new(1, 1)),
257        Health::new(100),
258    ));
259    
260    // Enemies
261    let goblin = world.spawn((
262        Position::new(SquareCoord::<FourConnected>::new(5, 2)),
263        Health::new(30),
264    ));
265    
266    let orc = world.spawn((
267        Position::new(SquareCoord::<FourConnected>::new(6, 4)),
268        Health::new(60),
269    ));
270    
271    // Treasure chest (no health, just position)
272    let treasure = world.spawn((
273        Position::new(SquareCoord::<FourConnected>::new(3, 5)),
274    ));
275    
276    println!("Created entities:");
277    println!("  Player: {:?}", player);
278    println!("  Goblin: {:?}", goblin);
279    println!("  Orc: {:?}", orc);  
280    println!("  Treasure: {:?}", treasure);
281    
282    // Count entities by type
283    let mut entity_count = 0;
284    let mut entities_with_health = 0;
285    
286    // Query all entities with positions
287    for (entity, (pos, health)) in world.query::<(&Position<SquareCoord<FourConnected>>, Option<&Health>)>().iter() {
288        entity_count += 1;
289        if health.is_some() {
290            entities_with_health += 1;
291        }
292        
293        let entity_type = if entity == player {
294            "Player"
295        } else if entity == goblin {
296            "Goblin"  
297        } else if entity == orc {
298            "Orc"
299        } else if entity == treasure {
300            "Treasure"
301        } else {
302            "Unknown"
303        };
304        
305        if let Some(hp) = health {
306            println!("  {} at ({}, {}): {}/{} HP", 
307                entity_type, pos.coord.x, pos.coord.y, hp.current, hp.maximum);
308        } else {
309            println!("  {} at ({}, {}): No health", 
310                entity_type, pos.coord.x, pos.coord.y);
311        }
312    }
313    
314    println!("\nEntity summary:");
315    println!("  Total entities: {}", entity_count);
316    println!("  Living entities: {}", entities_with_health);
317    
318    println!("āœ… Different entity types add variety to your game world!\n");
319}
320
321// Step 6: Simple combat system
322fn tutorial_step_6_combat() {
323    println!("āš”ļø Step 6: Simple Combat System");
324    println!("-------------------------------");
325    
326    // Set up entities for combat demo
327    let mut world = World::new();
328    let mut resource_manager = ResourceManager::new();
329    
330    // Create combatants
331    let player = world.spawn((
332        Position::new(SquareCoord::<FourConnected>::new(2, 2)),
333        Health::new(100),
334    ));
335    
336    let goblin = world.spawn((
337        Position::new(SquareCoord::<FourConnected>::new(3, 2)),
338        Health::new(30),
339    ));
340    
341    // Add to resource manager for easier health management
342    resource_manager.add_entity(player.id() as u32, 100.0, 50.0);
343    resource_manager.add_entity(goblin.id() as u32, 30.0, 0.0);
344    
345    println!("Combat scenario: Player vs Goblin");
346    println!("Player at (2,2) - Goblin at (3,2)");
347    
348    // Check if entities are adjacent (can attack)
349    let mut player_pos = None;
350    let mut goblin_pos = None;
351    
352    for (entity, pos) in world.query::<&Position<SquareCoord<FourConnected>>>().iter() {
353        if entity == player {
354            player_pos = Some(pos.coord);
355        } else if entity == goblin {
356            goblin_pos = Some(pos.coord);
357        } else {
358            // No action needed for other entities
359        }
360    }
361    
362    if let (Some(player_coord), Some(goblin_coord)) = (player_pos, goblin_pos) {
363        let distance = player_coord.distance(&goblin_coord);
364        println!("Distance between combatants: {}", distance);
365        
366        if distance == 1 {
367            println!("Combatants are adjacent - combat can begin!");
368            
369            // Simulate a few rounds of combat
370            for round in 1..=3 {
371                println!("\n--- Round {} ---", round);
372                
373                // Player attacks goblin
374                let player_damage = 15;
375                resource_manager.modify_health(goblin.id() as u32, -(player_damage as f32));
376                
377                if let Some(goblin_resources) = resource_manager.get_resources(goblin.id() as u32) {
378                    println!("Player attacks for {} damage!", player_damage);
379                    println!("Goblin health: {}/{}", 
380                        goblin_resources.health.current, 
381                        goblin_resources.health.maximum);
382                    
383                    if goblin_resources.health.current <= 0.0 {
384                        println!("šŸ’€ Goblin defeated!");
385                        break;
386                    }
387                }
388                
389                // Goblin attacks back (if alive)
390                if let Some(goblin_resources) = resource_manager.get_resources(goblin.id() as u32) {
391                    if goblin_resources.health.current > 0.0 {
392                        let goblin_damage = 8;
393                        resource_manager.modify_health(player.id() as u32, -(goblin_damage as f32));
394                        
395                        if let Some(player_resources) = resource_manager.get_resources(player.id() as u32) {
396                            println!("Goblin attacks for {} damage!", goblin_damage);
397                            println!("Player health: {}/{}", 
398                                player_resources.health.current, 
399                                player_resources.health.maximum);
400                        }
401                    }
402                }
403            }
404        } else {
405            println!("Combatants are too far apart for melee combat");
406        }
407    }
408    
409    println!("āœ… Combat systems bring challenge and interaction to your game!\n");
410}
411
412// Step 7: Visual debugging to see what's happening
413fn tutorial_step_7_debugging() {
414    println!("šŸ” Step 7: Visual Debugging");
415    println!("---------------------------");
416    println!("Debugging tools help you visualize and understand your game state.");
417    
418    // Create a debug renderer for our game world
419    let mut debug_renderer = GridRenderer::new()
420        .with_size(10, 8)
421        .with_style(GridStyle::Square4);
422    
423    // Add player
424    debug_renderer.add_colored_marker((2, 2), "P", "Player", DebugColor::Green, 20);
425    
426    // Add enemies
427    debug_renderer.add_colored_marker((5, 3), "G", "Goblin", DebugColor::Red, 15);
428    debug_renderer.add_colored_marker((7, 5), "O", "Orc", DebugColor::Red, 15);
429    
430    // Add treasure
431    debug_renderer.add_colored_marker((8, 1), "T", "Treasure", DebugColor::Yellow, 10);
432    
433    // Add walls/obstacles
434    let walls = vec![(3, 1), (3, 2), (3, 3), (6, 4), (6, 5), (6, 6)];
435    for (x, y) in walls {
436        debug_renderer.add_colored_marker((x, y), "#", "Wall", DebugColor::Gray, 5);
437    }
438    
439    // Show player's movement path
440    let path = vec![(2, 2), (1, 2), (1, 3), (1, 4), (2, 4), (4, 4), (5, 4), (5, 3)];
441    debug_renderer.add_path(path, "Player Path", DebugColor::Blue);
442    
443    println!("Game world visualization:");
444    println!("{}", debug_renderer.render_ascii());
445    
446    // Demonstrate debug annotations
447    debug_renderer.clear(); // Reset for cleaner view
448    
449    // Add just key elements with annotations
450    debug_renderer.add_colored_marker((2, 2), "P", "Player (HP: 85/100)", DebugColor::Green, 20);
451    debug_renderer.add_colored_marker((5, 3), "G", "Goblin (HP: 15/30)", DebugColor::Red, 15);
452    debug_renderer.add_annotation((2, 1), "Start", DebugColor::Blue);
453    debug_renderer.add_annotation((8, 1), "Goal", DebugColor::Yellow);
454    
455    println!("\nDetailed game state with annotations:");
456    println!("{}", debug_renderer.render_ascii());
457    
458    println!("āœ… Visual debugging is crucial for understanding complex game states!\n");
459}
460
461// Step 8: Putting it all together in a complete mini-game
462fn tutorial_step_8_complete_game() {
463    println!("šŸŽÆ Step 8: Complete Mini-Game");
464    println!("-----------------------------");
465    println!("Let's put everything together into a working game!");
466    
467    // Game components
468    let mut world = World::new();
469    let mut turn_game = TurnBasedGame::new();
470    let mut resource_manager = ResourceManager::new();
471    let mut event_bus = EventBus::new();
472    let mut state_machine = GameStateMachine::new(GameState::Playing);
473    
474    // Game events
475    #[derive(Debug, Clone)]
476    struct PlayerMoved {
477        from: (i32, i32),
478        to: (i32, i32),
479    }
480    
481    #[derive(Debug, Clone)] 
482    struct EnemyDefeated {
483        enemy_type: String,
484        position: (i32, i32),
485    }
486    
487    #[derive(Debug, Clone)]
488    struct TreasureFound {
489        position: (i32, i32),
490        value: u32,
491    }
492    
493    // Events automatically implement Event trait via blanket impl
494    
495    // Set up event listeners
496    event_bus.subscribe(|event: &PlayerMoved| {
497        println!("šŸ“ Player moved from ({}, {}) to ({}, {})", 
498            event.from.0, event.from.1, event.to.0, event.to.1);
499        EventResult::Continue
500    });
501    
502    event_bus.subscribe(|event: &EnemyDefeated| {
503        println!("šŸ’€ {} defeated at ({}, {})!", 
504            event.enemy_type, event.position.0, event.position.1);
505        EventResult::Continue
506    });
507    
508    event_bus.subscribe(|event: &TreasureFound| {
509        println!("šŸ’° Found treasure worth {} gold at ({}, {})!", 
510            event.value, event.position.0, event.position.1);
511        EventResult::Continue
512    });
513    
514    // Create game entities
515    let player = world.spawn((
516        Position::new(SquareCoord::<FourConnected>::new(1, 1)),
517        Health::new(100),
518    ));
519    
520    let goblin = world.spawn((
521        Position::new(SquareCoord::<FourConnected>::new(4, 2)),
522        Health::new(25),
523    ));
524    
525    let _treasure = world.spawn((
526        Position::new(SquareCoord::<FourConnected>::new(6, 3)),
527    ));
528    
529    // Add to game systems
530    turn_game.add_participant(player.id() as u32, 100); // Player goes first
531    turn_game.add_participant(goblin.id() as u32, 80);  // Goblin second
532    
533    resource_manager.add_entity(player.id() as u32, 100.0, 30.0);
534    resource_manager.add_entity(goblin.id() as u32, 25.0, 0.0);
535    
536    println!("šŸŽ® Mini Dungeon Explorer Started!");
537    println!("Player Goal: Defeat the goblin and find the treasure");
538    
539    // Game state tracking
540    let mut player_gold = 0;
541    let mut enemies_defeated = 0;
542    
543    // Simulate simple gameplay
544    println!("\n=== Game Simulation ===");
545    
546    // Player moves towards goblin
547    let player_start = SquareCoord::<FourConnected>::new(1, 1);
548    let goblin_pos = SquareCoord::<FourConnected>::new(4, 2);
549    
550    event_bus.publish(PlayerMoved { 
551        from: (player_start.x, player_start.y), 
552        to: (goblin_pos.x - 1, goblin_pos.y) 
553    });
554    
555    // Combat occurs
556    println!("āš”ļø Player attacks goblin!");
557    resource_manager.modify_health(goblin.id() as u32, -25.0); // Defeat goblin
558    
559    event_bus.publish(EnemyDefeated {
560        enemy_type: "Goblin".to_string(),
561        position: (goblin_pos.x, goblin_pos.y),
562    });
563    enemies_defeated += 1;
564    
565    // Player finds treasure
566    let treasure_pos = SquareCoord::<FourConnected>::new(6, 3);
567    event_bus.publish(TreasureFound {
568        position: (treasure_pos.x, treasure_pos.y),
569        value: 100,
570    });
571    player_gold += 100;
572    
573    // Victory condition
574    state_machine.process_event(GameStateEvent::VictoryAchieved);
575    println!("šŸŽ‰ Victory! Game completed!");
576    
577    event_bus.process_events();
578    
579    // Final game summary
580    println!("\nšŸ“‹ Game Summary:");
581    println!("   Final State: {:?}", state_machine.current_state());
582    println!("   Player Gold: {}", player_gold);
583    println!("   Enemies Defeated: {}", enemies_defeated);
584    
585    if state_machine.current_state() == GameState::Victory {
586        println!("šŸ† Congratulations! You've mastered the basics of tiles_tools!");
587    } else {
588        println!("ā° Game ended - but you've learned the fundamentals!");
589    }
590    
591    println!("āœ… You've built a complete tile-based game using tiles_tools!\n");
592}