mirror of
				https://github.com/gonum/gonum.git
				synced 2025-10-31 02:26:59 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			292 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			292 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright ©2014 The Gonum Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package internal
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"math"
 | |
| 
 | |
| 	"gonum.org/v1/gonum/graph"
 | |
| 	"gonum.org/v1/gonum/graph/simple"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	Closed  = '*' // Closed is the closed grid node representation.
 | |
| 	Open    = '.' // Open is the open grid node repesentation.
 | |
| 	Unknown = '?' // Unknown is the unknown grid node repesentation.
 | |
| )
 | |
| 
 | |
| // Grid is a 2D grid planar undirected graph.
 | |
| type Grid struct {
 | |
| 	// AllowDiagonal specifies whether
 | |
| 	// diagonally adjacent nodes can
 | |
| 	// be connected by an edge.
 | |
| 	AllowDiagonal bool
 | |
| 	// UnitEdgeWeight specifies whether
 | |
| 	// finite edge weights are returned as
 | |
| 	// the unit length. Otherwise edge
 | |
| 	// weights are the Euclidean distance
 | |
| 	// between connected nodes.
 | |
| 	UnitEdgeWeight bool
 | |
| 
 | |
| 	// AllVisible specifies whether
 | |
| 	// non-open nodes are visible
 | |
| 	// in calls to Nodes and HasNode.
 | |
| 	AllVisible bool
 | |
| 
 | |
| 	open []bool
 | |
| 	r, c int
 | |
| }
 | |
| 
 | |
| // NewGrid returns an r by c grid with all positions
 | |
| // set to the specified open state.
 | |
| func NewGrid(r, c int, open bool) *Grid {
 | |
| 	states := make([]bool, r*c)
 | |
| 	if open {
 | |
| 		for i := range states {
 | |
| 			states[i] = true
 | |
| 		}
 | |
| 	}
 | |
| 	return &Grid{
 | |
| 		open: states,
 | |
| 		r:    r,
 | |
| 		c:    c,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // NewGridFrom returns a grid specified by the rows strings. All rows must
 | |
| // be the same length and must only contain the Open or Closed characters,
 | |
| // NewGridFrom will panic otherwise.
 | |
| func NewGridFrom(rows ...string) *Grid {
 | |
| 	if len(rows) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 	for i, r := range rows[:len(rows)-1] {
 | |
| 		if len(r) != len(rows[i+1]) {
 | |
| 			panic("grid: unequal row lengths")
 | |
| 		}
 | |
| 	}
 | |
| 	states := make([]bool, 0, len(rows)*len(rows[0]))
 | |
| 	for _, r := range rows {
 | |
| 		for _, b := range r {
 | |
| 			switch b {
 | |
| 			case Closed:
 | |
| 				states = append(states, false)
 | |
| 			case Open:
 | |
| 				states = append(states, true)
 | |
| 			default:
 | |
| 				panic(fmt.Sprintf("grid: invalid state: %q", r))
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return &Grid{
 | |
| 		open: states,
 | |
| 		r:    len(rows),
 | |
| 		c:    len(rows[0]),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Nodes returns all the open nodes in the grid if AllVisible is
 | |
| // false, otherwise all nodes are returned.
 | |
| func (g *Grid) Nodes() []graph.Node {
 | |
| 	var nodes []graph.Node
 | |
| 	for id, ok := range g.open {
 | |
| 		if ok || g.AllVisible {
 | |
| 			nodes = append(nodes, simple.Node(id))
 | |
| 		}
 | |
| 	}
 | |
| 	return nodes
 | |
| }
 | |
| 
 | |
| // Has returns whether n is a node in the grid. The state of
 | |
| // the AllVisible field determines whether a non-open node is
 | |
| // present.
 | |
| func (g *Grid) Has(id int64) bool {
 | |
| 	return 0 <= id && id < int64(len(g.open)) && (g.AllVisible || g.open[id])
 | |
| }
 | |
| 
 | |
| // HasOpen returns whether n is an open node in the grid.
 | |
| func (g *Grid) HasOpen(id int64) bool {
 | |
| 	return 0 <= id && id < int64(len(g.open)) && g.open[id]
 | |
| }
 | |
| 
 | |
| // Set sets the node at position (r, c) to the specified open state.
 | |
| func (g *Grid) Set(r, c int, open bool) {
 | |
| 	if r < 0 || r >= g.r {
 | |
| 		panic("grid: illegal row index")
 | |
| 	}
 | |
| 	if c < 0 || c >= g.c {
 | |
| 		panic("grid: illegal column index")
 | |
| 	}
 | |
| 	g.open[r*g.c+c] = open
 | |
| }
 | |
| 
 | |
| // Dims returns the dimensions of the grid.
 | |
| func (g *Grid) Dims() (r, c int) {
 | |
| 	return g.r, g.c
 | |
| }
 | |
| 
 | |
| // RowCol returns the row and column of the id. RowCol will panic if the
 | |
| // node id is outside the range of the grid.
 | |
| func (g *Grid) RowCol(id int64) (r, c int) {
 | |
| 	if id < 0 || int64(len(g.open)) <= id {
 | |
| 		panic("grid: illegal node id")
 | |
| 	}
 | |
| 	return int(id) / g.c, int(id) % g.c
 | |
| }
 | |
| 
 | |
| // XY returns the cartesian coordinates of n. If n is not a node
 | |
| // in the grid, (NaN, NaN) is returned.
 | |
| func (g *Grid) XY(id int64) (x, y float64) {
 | |
| 	if !g.Has(id) {
 | |
| 		return math.NaN(), math.NaN()
 | |
| 	}
 | |
| 	r, c := g.RowCol(id)
 | |
| 	return float64(c), float64(r)
 | |
| }
 | |
| 
 | |
| // NodeAt returns the node at (r, c). The returned node may be open or closed.
 | |
| func (g *Grid) NodeAt(r, c int) graph.Node {
 | |
| 	if r < 0 || r >= g.r || c < 0 || c >= g.c {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return simple.Node(r*g.c + c)
 | |
| }
 | |
| 
 | |
| // From returns all the nodes reachable from u. Reachabilty requires that both
 | |
| // ends of an edge must be open.
 | |
| func (g *Grid) From(uid int64) []graph.Node {
 | |
| 	if !g.HasOpen(uid) {
 | |
| 		return nil
 | |
| 	}
 | |
| 	nr, nc := g.RowCol(uid)
 | |
| 	var to []graph.Node
 | |
| 	for r := nr - 1; r <= nr+1; r++ {
 | |
| 		for c := nc - 1; c <= nc+1; c++ {
 | |
| 			if v := g.NodeAt(r, c); v != nil && g.HasEdgeBetween(uid, v.ID()) {
 | |
| 				to = append(to, v)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return to
 | |
| }
 | |
| 
 | |
| // HasEdgeBetween returns whether there is an edge between u and v.
 | |
| func (g *Grid) HasEdgeBetween(uid, vid int64) bool {
 | |
| 	if !g.HasOpen(uid) || !g.HasOpen(vid) || uid == vid {
 | |
| 		return false
 | |
| 	}
 | |
| 	ur, uc := g.RowCol(uid)
 | |
| 	vr, vc := g.RowCol(vid)
 | |
| 	if abs(ur-vr) > 1 || abs(uc-vc) > 1 {
 | |
| 		return false
 | |
| 	}
 | |
| 	return g.AllowDiagonal || ur == vr || uc == vc
 | |
| }
 | |
| 
 | |
| func abs(i int) int {
 | |
| 	if i < 0 {
 | |
| 		return -i
 | |
| 	}
 | |
| 	return i
 | |
| }
 | |
| 
 | |
| // Edge returns the edge between u and v.
 | |
| func (g *Grid) Edge(uid, vid int64) graph.Edge {
 | |
| 	return g.WeightedEdgeBetween(uid, vid)
 | |
| }
 | |
| 
 | |
| // WeightedEdge returns the weighted edge between u and v.
 | |
| func (g *Grid) WeightedEdge(uid, vid int64) graph.WeightedEdge {
 | |
| 	return g.WeightedEdgeBetween(uid, vid)
 | |
| }
 | |
| 
 | |
| // EdgeBetween returns the edge between u and v.
 | |
| func (g *Grid) EdgeBetween(uid, vid int64) graph.Edge {
 | |
| 	return g.WeightedEdgeBetween(uid, vid)
 | |
| }
 | |
| 
 | |
| // WeightedEdgeBetween returns the weighted edge between u and v.
 | |
| func (g *Grid) WeightedEdgeBetween(uid, vid int64) graph.WeightedEdge {
 | |
| 	if g.HasEdgeBetween(uid, vid) {
 | |
| 		if !g.AllowDiagonal || g.UnitEdgeWeight {
 | |
| 			return simple.WeightedEdge{F: simple.Node(uid), T: simple.Node(vid), W: 1}
 | |
| 		}
 | |
| 		ux, uy := g.XY(uid)
 | |
| 		vx, vy := g.XY(vid)
 | |
| 		return simple.WeightedEdge{F: simple.Node(uid), T: simple.Node(vid), W: math.Hypot(ux-vx, uy-vy)}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Weight returns the weight of the given edge.
 | |
| func (g *Grid) Weight(xid, yid int64) (w float64, ok bool) {
 | |
| 	if xid == yid {
 | |
| 		return 0, true
 | |
| 	}
 | |
| 	if !g.HasEdgeBetween(xid, yid) {
 | |
| 		return math.Inf(1), false
 | |
| 	}
 | |
| 	if e := g.EdgeBetween(xid, yid); e != nil {
 | |
| 		if !g.AllowDiagonal || g.UnitEdgeWeight {
 | |
| 			return 1, true
 | |
| 		}
 | |
| 		ux, uy := g.XY(e.From().ID())
 | |
| 		vx, vy := g.XY(e.To().ID())
 | |
| 		return math.Hypot(ux-vx, uy-vy), true
 | |
| 	}
 | |
| 	return math.Inf(1), true
 | |
| }
 | |
| 
 | |
| // String returns a string representation of the grid.
 | |
| func (g *Grid) String() string {
 | |
| 	b, _ := g.Render(nil)
 | |
| 	return string(b)
 | |
| }
 | |
| 
 | |
| // Render returns a text representation of the graph
 | |
| // with the given path included. If the path is not a path
 | |
| // in the grid Render returns a non-nil error and the
 | |
| // path up to that point.
 | |
| func (g *Grid) Render(path []graph.Node) ([]byte, error) {
 | |
| 	b := make([]byte, g.r*(g.c+1)-1)
 | |
| 	for r := 0; r < g.r; r++ {
 | |
| 		for c := 0; c < g.c; c++ {
 | |
| 			if g.open[r*g.c+c] {
 | |
| 				b[r*(g.c+1)+c] = Open
 | |
| 			} else {
 | |
| 				b[r*(g.c+1)+c] = Closed
 | |
| 			}
 | |
| 		}
 | |
| 		if r < g.r-1 {
 | |
| 			b[r*(g.c+1)+g.c] = '\n'
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// We don't use topo.IsPathIn at the outset because we
 | |
| 	// want to draw as much as possible before failing.
 | |
| 	for i, n := range path {
 | |
| 		id := n.ID()
 | |
| 		if !g.Has(id) || (i != 0 && !g.HasEdgeBetween(path[i-1].ID(), id)) {
 | |
| 			if 0 <= id && id < int64(len(g.open)) {
 | |
| 				r, c := g.RowCol(id)
 | |
| 				b[r*(g.c+1)+c] = '!'
 | |
| 			}
 | |
| 			return b, errors.New("grid: not a path in graph")
 | |
| 		}
 | |
| 		r, c := g.RowCol(id)
 | |
| 		switch i {
 | |
| 		case len(path) - 1:
 | |
| 			b[r*(g.c+1)+c] = 'G'
 | |
| 		case 0:
 | |
| 			b[r*(g.c+1)+c] = 'S'
 | |
| 		default:
 | |
| 			b[r*(g.c+1)+c] = 'o'
 | |
| 		}
 | |
| 	}
 | |
| 	return b, nil
 | |
| }
 | 
