Softanza
StartFrançais
Softanza
Research · narration

The Navigator's Grid: Mastering stzGrid in the Softanza Library

stzGrid offers a one-based coordinate system (starting at position [1,1]) for navigating grid spaces without storing data elements itself. Think of it as an intelligent cursor that can move throughout a grid while providing information about its surroundings.

by Mansour Ayouni · in the repository since 2025-05-01 · tutorial · no single Atlas area

The stzGrid class is a powerful navigational framework in the Softanza library, offering elegant solutions for working with two-dimensional grid structures. This article uses visual examples and practical applications to help you fully grasp its capabilities.

Introduction to stzGrid

Let's see what happens when we create a basic 5×5 grid:

Haro
load "stzlib.ring"

o1 = new stzGrid([5, 5])

# Grid Size
? o1.NumberOfRows()    #--> 5
? o1.NumberOfColumns() #--> 5
? o1.Size() #--> 25
? o1.SizeXT() #--> [5, 5]

# Current Position
? o1.CurrentPosition()  #--> [1, 1]

illustration

When visualized, our new grid looks like this:

    1 2 3 4 5 
  ╭─v─────────╮
1 > x . . . . │  ← The 'x' shows our cursor at position [1,1]
2 │ . . . . . │
3 │ . . . . . │
4 │ . . . . . │
5 │ . . . . . │
  ╰───────────╯

Basic Movement: Navigating Your Grid

Cardinal Directions

stzGrid provides intuitive methods for moving in four directions:

Haro
StzGridQ([5, 5]) {

	# Starting at [1,1]
	MoveDown()  # Move down once
	MoveDown()  # Move down again
	? CurrentPosition()  #--> [1, 3]

	MoveRight() # Move right once
	MoveRight() # Move right again
	MoveRight() # Move right a third time
	? CurrentPosition()  #--> [4, 3]

	Show()
}

illustration

This produces:

    1 2 3 4 5 
  ╭───────v───╮
1 │ . . . . . │
2 │ . . . . . │
3 > . . . x . │  ← We're now at position [4,3]
4 │ . . . . . │
5 │ . . . . . │
  ╰───────────╯

Boundary Intelligence

stzGrid prevents movement beyond grid boundaries:

Haro
o1 = new stzGrid([6, 4])

# Move to edge
o1.MoveTo(6, 1)  # Position [6,1]

# Try to move beyond boundary
o1.MoveRight()   # Won't move
? o1.Position()  # Still [6,1]

illustration

Precision Movement

For direct positioning:

Haro
o1 = new stzGrid([10, 10])
o1.MoveTo(3, 3)  # Jump directly to position [3,3]

# Now make a larger movement
o1.MoveBy(4, 3)  # Move 4 columns right, 3 rows down
? o1.Position()  # [7, 6]

o1.Show()

illustration

Resulting in:

    1 2 3 4 5 6 7 8 9 0 
  ╭─────────────v───────╮
1 │ . . . . . . . . . . │
2 │ . . . . . . . . . . │
3 │ . . . . . . . . . . │
4 │ . . . . . . . . . . │
5 │ . . . . . . . . . . │
6 > . . . . . . x . . . │  ← Now at position [7,6]
7 │ . . . . . . . . . . │
8 │ . . . . . . . . . . │
9 │ . . . . . . . . . . │
0 │ . . . . . . . . . . │
  ╰─────────────────────╯

Understanding Direction-Based Movement

stzGrid maintains a "current direction" concept that enhances movement capabilities:

Haro
StzGridQ([4, 4]) {

	# Check default direction
	? Direction()  #--> "forward"

	# Change direction
	SetDirection(:down)
	? Direction()  #--> "down"

	# Move to center
	MoveTo(2, 2)

	# Move in current direction (down)
	MoveToNextPosition()
	? Position()  #--> [2, 3]

	# Move opposite direction (up)
	MoveToPreviousPosition()
	? Position()  #--> [2, 2]
}

illustration

This direction system becomes particularly useful for algorithms that need to traverse paths in specific ways.

Exploring Nearby Positions

One of stzGrid's most powerful features is its ability to analyze neighboring positions:

Haro
o1 = new stzGrid([5, 5])
o1.MoveTo(3, 3)  #--> Center of grid

# Adjacent positions
? o1.NodeAbove()      #--> [3, 2]
? o1.NodeBelow()      #--> [3, 4]
? o1.NodeToLeft()     #--> [2, 3]
? o1.NodeToRight()    #--> [4, 3]

# Diagonal positions
? o1.NodeAboveLeft()  #--> [2, 2]
? o1.NodeAboveRight() #--> [4, 2]
? o1.NodeBelowLeft()  #--> [2, 4]
? o1.NodeBelowRight() #--> [4, 4]

# All surrounding nodes
? o1.Neighbors()	# Or AdjacentNodes() if you prefer
# [[2,2], [2,3], [2,4], [3,2], [3,4], [4,2], [4,3], [4,4]]

o1.ShowNeighbors()

illustration

This creates a visual representation of the neighborhood:

    1 2 3 4 5 
  ╭─────v─────╮
1 │ . . . . . │
2 │ . N N N . │  ← 'N' shows neighboring positions
3 > . N x N . │  ← 'x' is our current position at [3,3]
4 │ . N N N . │
5 │ . . . . . │
  ╰───────────╯

Distance Calculations: Measuring Spatial Relationships

stzGrid supports both Manhattan (grid-based) and Euclidean (straight-line) distance:

Haro
StzGridQ([10, 10]) {
  MoveTo(3, 3)
  
  # Manhattan Distance (Sum of horizontal and vertical distances)
  ? DistanceTo(7, 6)  #--> 7
  
  # Euclidean Distance (Straight line "as the crow flies")
  ? EuclideanDistanceTo(7, 6)  #--> 5
}

illustration

Adding Obstacles: Creating Complex Spaces

Obstacles transform simple grids into complex environments:

Haro
o1 = new stzGrid([10, 6])

# Add some obstacles one by one
o1.AddObstacle(3, 2)
o1.AddObstacle(4, 2)
o1.AddObstacle(5, 2)
o1.AddObstacle(3, 4)
o1.AddObstacle(4, 4)
o1.AddObstacle(5, 4)

# Show grid with obstacles
o1.Show()

illustration

Resulting in:

    1 2 3 4 5 6 7 8 9 0 
  ╭─────────────────────╮
1 │ x . . . . . . . . . │
2 │ . . ■ ■ ■ . . . . . │  ← Row of obstacles
3 │ . . . . . . . . . . │
4 │ . . ■ ■ ■ . . . . . │  ← Another row of obstacles
5 │ . . . . . . . . . . │
6 │ . . . . . . . . . . │
  ╰─────────────────────╯

You can customize obstacle appearance:

Haro
o1.SetObstacleChar("B")  # Change obstacle character
o1.Show()

illustration

Now obstacles appear as:

    1 2 3 4 5 6 7 8 9 0 
  ╭─────────────────────╮
1 │ x . . . . . . . . . │
2 │ . . B B B . . . . . │  ← Obstacles now shown as 'B'
3 │ . . . . . . . . . . │
4 │ . . B B B . . . . . │
5 │ . . . . . . . . . . │
6 │ . . . . . . . . . . │
  ╰─────────────────────╯

Path Creation and Management

stzGrid excels at creating and managing paths between points:

Haro
StzGridQ([8, 6]) {
  # Add many obstacles in one command
  AddObstacles([ [3, 2], [3, 3], [3, 4], [5, 2], [5, 3], [5, 4] ])
  
  # Create a path manually by specifying the traversed nodes
  AddPath([
    [1, 1], [2, 1],
    [2, 2], [4, 2],
    [2, 3], [4, 3],
    [2, 4], [4, 4], [6, 2],
    [6, 3], [6, 4], [7, 4], [8, 4]
  ])
  
  # Path information
  ? PathLength()  #--> 13
  
  # Move to end of path and show
  MoveTo(8, 4)
  Show()
}

illustration

The visual result:

    1 2 3 4 5 6 7 8 
  ╭───────────────v─╮
1 │ ○ ○ · · · · · · │  ← '○' shows path nodes
2 │ · ○ ■ · ■ ○ · · │  ← '■' shows obstacles
3 │ · ○ ■ ○ ■ ○ · · │
4 > · ○ ■ ○ ■ ○ ○ x │  ← 'x' is our position at [8,4]
5 │ · · · · · · · · │
6 │ · · · · · · · · │
  ╰─────────────────╯

Powerful Pathfinding: Finding Routes Automatically

Instead of manually creating paths, stzGrid offers several pathfinding algorithms:

A* Algorithm - The Shortest Path

Let's construct a grid with some vertical obstacle lines, and then find the shortest path between two nodes while navigating around these obstacles.

Haro
StzGridQ([10, 6]) {
  # Add obstacles to form a maze
  AddObstacles([ [3, 2], [3, 3], [3, 4] ])
  AddObstacles([ [6, 1], [6, 2], [6, 3] ])
  AddObstacles([ [8, 3], [8, 4], [8, 5] ])
  
  # Find shortest path from [1,1] to [10,6]
  ShortestPath([1, 1], [10, 6])
  Show()
}

illustration

The A* algorithm, used internally by ShortestPath() function generates an optimal path around obstacles:

    1 2 3 4 5 6 7 8 9 0 
  ╭─v───────────────────╮
1 > x . . . . ■ . . . . │
2 │ ○ . ■ . . ■ . . . . │
3 │ ○ . ■ . . ■ . ■ . . │
4 │ ○ . ■ . . . . ■ . . │
5 │ ○ . . . . . . ■ . . │
6 │ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ │  ← Path shown as '○' for each traversed node
  ╰─────────────────────╯

Manhattan Path - Efficient Grid Movement

stzGrid enables automatic path definition using a Manhattan traversal strategy—either horizontal-first or vertical-first.

Haro
StzGridQ([10, 6]) {
  # Horizontal-first Manhattan path
  ManhattanPath([1, 1], [10, 6])
  Show()
}

illustration

This creates a path that moves horizontally first, then vertically:

    1 2 3 4 5 6 7 8 9 0 
  ╭─v───────────────────╮
1 > x ○ ○ ○ ○ ○ ○ ○ ○ ○ │  ← Move horizontally first
2 │ . . . . . . . . . ○ │
3 │ . . . . . . . . . ○ │  ← Then move vertically
4 │ . . . . . . . . . ○ │
5 │ . . . . . . . . . ○ │
6 │ . . . . . . . . . ○ │
  ╰─────────────────────╯

To let it move vertically-first, use ManhattanPathXT([1, 1], [10, 6], :Vertical) and you get:

    1 2 3 4 5 6 7 8 9 0 
  ╭─v───────────────────╮
1 > x . . . . . . . . . │  ← Move vertically first
2 │ ○ . . . . . . . . . │
3 │ ○ . . . . . . . . . │
4 │ ○ . . . . . . . . . │
5 │ ○ . . . . . . . . . │
6 │ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ │  ← Then move horizontally
  ╰─────────────────────╯

Note : The name "Manhattan distance" comes from the layout of Manhattan, New York City, which is famous for its grid-like street system. In this grid, streets typically run north-south and east-west, forming a rectangular pattern. When navigating such a city, a person or vehicle usually cannot move diagonally—only along the streets—so the shortest path between two points is the sum of the horizontal and vertical distances, not the straight-line (diagonal) distance.

Special Path Types: Spiral and ZigZag

Other advanced traversal strategies that may be useful in practice—such as Spiral and ZigZag—are also available.

Haro
StzGridQ([10, 6]) {
  # Create spiral path with 3 rings
  SpiralPath([5, 3], :Rings = 3)
  Show()
}

illustration

This creates a spiral pattern:

    1 2 3 4 5 6 7 8 9 0 
  ╭─v───────────────────╮
1 > x . ○ ○ ○ ○ ○ . . . │
2 │ . . ○ ○ ○ ○ ○ . . . │
3 │ . . ○ ○ ● ○ ○ . . . │  ← Spiral pattern from center
4 │ . . ○ ○ ○ ○ ○ . . . │
5 │ . . ○ ○ ○ ○ ○ . . . │
6 │ . . . . . . . . . . │
  ╰─────────────────────╯

Region Analysis: Understanding Grid Connectivity

stzGrid provides powerful analysis of connected regions—defined as contiguous nodes enclosed by borders and/or closed obstacles.

Haro
o1 = new stzGrid([10, 6])

# Create barriers dividing the grid
o1.AddObstacles([
  [3, 1], [3, 2], [3, 3], [3, 4], [3, 6],
  [7, 1], [7, 2], [7, 3], [7, 4], [7, 5], [7, 6]
])

o1.Show()

illustration

This creates two separate regions:

    1 2 3 4 5 6 7 8 9 0 
  ╭─v───────────────────╮
1 > x . ■ . . . ■ . . . │
2 │ . . ■ . . . ■ . . . │
3 │ . . ■ . . . ■ . . . │  ← Two vertical barrier lines
4 │ . . ■ . . . ■ . . . │
5 │ . . . . . . ■ . . . │
6 │ . . ■ . . . ■ . . . │
  ╰─────────────────────╯

Let's analyze the connectivity of various node pairs within such a grid structure:

Haro
# Test connectivity
? o1.AreConnected([1, 1], [2, 2])  #--> TRUE - same region
? o1.AreConnected([1, 1], [8, 6])  #--> FALSE - different regions

# Find and paint all connected regions
o1.ShowRegions()

illustration

This visually identifies the regions:

    1 2 3 4 5 6 7 8 9 0 
  ╭─v───────────────────╮
1 > 1 1 ■ 1 1 1 ■ 2 2 2 │
2 │ 1 1 ■ 1 1 1 ■ 2 2 2 │
3 │ 1 1 ■ 1 1 1 ■ 2 2 2 │  ← Regions automatically labeled '1' and '2'
4 │ 1 1 ■ 1 1 1 ■ 2 2 2 │
5 │ 1 1 1 1 1 1 ■ 2 2 2 │
6 │ 1 1 ■ 1 1 1 ■ 2 2 2 │
  ╰─────────────────────╯

If you want, you can paint the regions with your own characters, using the eXTended form of the same function:

o1.ShowRegionsXT(["A", "B"])   
#--> 
    1 2 3 4 5 6 7 8 9 0 
  ╭─v───────────────────╮
1 > A A ■ A A A ■ B B B │
2 │ A A ■ A A A ■ B B B │
3 │ A A ■ A A A ■ B B B │  ← Regions labeled 'A' and 'B'
4 │ A A ■ A A A ■ B B B │
5 │ A A A A A A ■ B B B │
6 │ A A ■ A A A ■ B B B │
  ╰─────────────────────╯

Maze Generation: Creating Complex Navigation Challenges

stzGrid provides built-in gamified maze generation capabilities:

Haro
StzGridQ([15, 8]) {
  # Generate a random maze with 30% obstacle density
  RandomMaze(30)
  Show()
}

illustration

This might produce:

    1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 
  ╭─v─────────────────────────────╮
1 > x . . . . ■ . ■ . . ■ ■ ■ ■ . │
2 │ . . . ■ . . . . . ■ ■ . . . . │
3 │ . . ■ . ■ . . . . . ■ . . . ■ │  ← Random maze with obstacles
4 │ . . . ■ . . . . . . . . . . ■ │
5 │ . . ■ ■ ■ ■ . . ■ . . . . ■ ■ │
6 │ . ■ ■ . . . . . . . . . . ■ . │
7 │ ■ ■ . . ■ . ■ . . ■ ■ . ■ . . │
8 │ . ■ . . . . . . ■ . . . . ■ . │
  ╰───────────────────────────────╯

Or create a maze with a guaranteed path from start to end:

Haro
StzGridQ([15, 8]) {
  # Generate maze with guaranteed path from [1,1] to [15,8]
  MazeWithPath([1, 1], [15, 8])
  Show()
}

illustration

This creates a maze with a valid solution that its guranteed to traverse the defined path:

    1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 
  ╭─v─────────────────────────────╮
1 > x . . ■ . . . . ■ ■ . ■ . . . │
2 │ ○ . ■ . . . . . ■ . . . . . . │
3 │ ○ . . . . . . . ■ . . . ■ . ■ │
4 │ ○ ■ . . ■ . ■ ■ . . . . ■ . . │
5 │ ○ . . . ■ ■ ■ . ■ . ■ . . . . │
6 │ ○ . . ■ ■ . . . . . . . . . . │
7 │ ○ . ■ . ■ . . . . ■ . . . . . │
8 │ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ │  ← Path shown by '○'
  ╰───────────────────────────────╯

Path Complexity Analysis: Measuring Path Quality

Beyond basic path creation and finding, stzGrid offers sophisticated tools to analyze path characteristics, helping you evaluate and optimize your navigation solutions.

Understanding Path Complexity

The PathComplexity() method counts the number of direction changes or "turns" in a path, providing a quantitative measure of navigational complexity:

Haro
StzGridQ([8, 6]) {
  # Create a simple path with several turns
  AddPath([
    [1, 1], [2, 1], [3, 1],  # Move right
    [3, 2], [3, 3],          # Move down
    [4, 3], [5, 3], [6, 3],  # Move right again
    [6, 4], [6, 5]           # Move down again
  ])
  
  # Check path complexity
  ? PathComplexity()  #--> 3  (Three turns in the path)
  Show()
}

illustration

This creates a path with distinct turns:

    1 2 3 4 5 6 7 8 
  ╭─v───────────────╮
1 > ○ ○ ○ · · · · · │
2 │ · · ○ · · · · · │  ← First turn (right to down)
3 │ · · ○ ○ ○ ○ · · │  ← Second turn (down to right)
4 │ · · · · · ○ · · │  ← Third turn (right to down)
5 │ · · · · · ○ · · │
6 │ · · · · · · · · │
  ╰─────────────────╯

When evaluating path complexity:

  • 0-1 turns: Simple, direct paths suitable for straight-line navigation
  • 2-4 turns: Moderately complex paths with reasonable navigation demands
  • 5+ turns: Complex paths that may require more detailed instructions or waypoints

The complexity value lets you compare multiple paths objectively—a path with 2 turns is inherently simpler to navigate than one with 6 turns covering similar distance.

Measuring Path Efficiency

The PathEfficiency() method calculates how efficient a path is compared to the direct Manhattan distance, returning a percentage value:

Haro
StzGridQ([10, 6]) {
  # First path - direct and efficient
  AddPath([
    [1, 1], [2, 1], [3, 1], [4, 1], [5, 1],
    [5, 2], [5, 3]
  ])
  
  ? PathEfficiency()  #--> 100.0  (Perfect efficiency)
  Show()
}

illustration

When visualized, the efficient path looks like this:

    1 2 3 4 5 6 7 8 9 0 
  ╭─v───────────────────╮
1 > ○ ○ ○ ○ ○ · · · · · │
2 │ · · · · ○ · · · · · │  ← Direct path with no wasted steps
3 │ · · · · ○ · · · · · │
4 │ · · · · · · · · · · │
5 │ · · · · · · · · · · │
6 │ · · · · · · · · · · │
  ╰─────────────────────╯

Now let's examine a less efficient path:

Haro
StzGridQ([10, 6]) {
  # Second path - winding and less efficient
  AddPath([
    [1, 1], [2, 1], [3, 1],
    [3, 2], [2, 2], [2, 3],
    [3, 3], [4, 3], [5, 3]
  ])
  
  ? PathEfficiency()  #--> 75.0  (Less efficient)
  Show()
}

illustration

This creates a meandering path with lower efficiency:

    1 2 3 4 5 6 7 8 9 0 
  ╭─v───────────────────╮
1 > ○ ○ ○ · · · · · · · │
2 │ · ○ ○ · · · · · · · │  ← Path loops back on itself
3 │ · ○ ○ ○ ○ · · · · · │
4 │ · · · · · · · · · · │
5 │ · · · · · · · · · · │
6 │ · · · · · · · · · · │
  ╰─────────────────────╯

Path efficiency provides a quantitative basis for comparing navigation solutions:

  • 100%: Optimal path with no wasted movement
  • 75-99%: Good efficiency, acceptable for most applications
  • 50-74%: Moderate efficiency, may need optimization
  • Below 50%: Poor efficiency, likely contains unnecessary detours

These analysis tools provide valuable metrics for evaluating and refining navigation strategies.

Practical Applications of stzGrid

As you could imagine, the stzGrid class offers solutions for a wide variety of practical problems:

Pathfinding and Navigation

  • Robot Navigation: Plan efficient paths for robots in warehouses
  • Traffic Simulation: Model vehicle movement through city grids
  • Evacuation Planning: Design optimal exit routes for buildings

Path Analysis and Optimization

  • Path Complexity Analysis: Quantify navigation complexity by measuring turns and direction changes
  • Efficiency Benchmarking: Compare path solutions based on optimality metrics
  • NPC Movement Enhancement: Design more natural character movement patterns in games
  • Transportation Optimization: Design routes with fewer turns to reduce congestion
  • UX Flow Improvement: Analyze and optimize user journeys by quantifying navigation steps

Game Development

  • Board Games: Create chess, checkers, or Go boards with position tracking
  • Roguelike Games: Generate procedural dungeons with guaranteed paths
  • Strategy Games: Implement unit movement with pathfinding on terrain maps
  • Puzzle Games: Create grid-based puzzles like Sokoban or sliding puzzles

Note: In this particular case, using stzTile—the square-based counterpart of stzGrid—would be more suitable, as it aligns better with the map metaphor commonly used in platform games. See examples in the stzTileTest.ring file : here(opens in a new tab).

Simulations

  • Cellular Automata: Implement Conway's Game of Life or other cellular automata
  • Flow Simulations: Model fluid dynamics on a grid
  • Population Models: Create agent-based simulations with movement rules

Image Processing

  • Flood Fill: Implement paint bucket tools
  • Edge Detection: Find boundaries between regions
  • Region Labeling: Identify connected components in binary images

Educational Tools

  • Maze Solving Visualizations: Demonstrate different pathfinding algorithms
  • Graph Theory Concepts: Visualize connectivity and spanning trees
  • Algorithm Animation: Show step-by-step execution of grid-based algorithms

The Softanza Advantage: Why stzGrid Stands Out

When compared to similar grid implementations in other languages and frameworks, stzGrid offers several distinct advantages:

Feature stzGrid (Softanza) Python (NumPy/networkx) JavaScript (Grid Libraries) C++ (Grid Implementations)
API Intuitiveness ✅ Very high - methods like MoveRight() are self-explanatory Moderate - requires understanding of array indexing Varies by library Typically low - requires understanding of implementation details
Visualization ✅ Built-in ASCII visualization Requires matplotlib or other visualization libraries Requires DOM manipulation or canvas Requires external visualization libraries
Pathfinding ✅ Multiple built-in algorithms (A*, Manhattan, ZigZag, Spiral) Requires networkx or custom implementation Usually requires separate libraries Requires custom implementation or libraries
Obstacle Handling ✅ Native support with collision detection Must be manually implemented Limited in most libraries Must be manually implemented
Region Analysis ✅ Built-in flood fill and connectivity testing Requires scipy or custom algorithms Usually limited Requires custom implementation
Movement Paradigm ✅ Rich directional movement with boundary awareness Basic array indexing Basic grid movement Basic coordinate management
Maze Generation ✅ Built-in algorithms Requires third-party libraries Varies by library Requires custom implementation
Learning Curve ✅ Gentle - intuitive naming conventions Steeper - requires understanding of array operations Varies by library Steep - requires deep programming knowledge
Code Readability ✅ High - o1.MoveToNextPosition() is self-documenting Moderate - grid[x+1][y] requires context Varies by library Often low due to implementation complexity

Conclusion: The Power of Grid Navigation

The stzGrid class offers a practical and flexible solution for working with grids, making navigation and analysis both intuitive and efficient. Like anything else in Softanza library, Its API is designed to simplify complex operations, turning what’s usually verbose and error-prone into clean, readable code.

By abstracting the complexity of grid-based operations—such as movement, pathfinding, obstacle handling, and region analysis—stzGrid makes it easier to integrate into games, simulations, or spatial computing tasks without getting bogged down in low-level details.

Cite

Mansour Ayouni. "The Navigator's Grid: Mastering stzGrid in the Softanza Library". Softanza narrations, 2025. https://mayouni.github.io/stzsite/en/narrations/stzgrid-navigator-grid-mastering.html

Not run: its code touches files, the network, the clock or chance. The article is published as written; its blocks are illustrations. The code of the articles is Haro code. Where an article was written before the language took its present name, the name was changed and nothing else. Genre, area and series read from the file's name, the year from its first commit: derived, until the article carries its own header. The file on GitHub(opens in a new tab)