strategy.go raw

   1  // Package strategy embeds Sun Tzu's Art of War as a structural layer in the
   2  // lattice. Strategic principles become both sticky elements (positive space)
   3  // and constraints (negative space), forming the system's permanent strategic
   4  // skeleton.
   5  //
   6  // The 13 chapters map to 13 strategic categories. Each category decomposes
   7  // into four levels: chapter, principle, maxim, and tactic.
   8  package strategy
   9  
  10  import (
  11  	_ "embed"
  12  	"strings"
  13  )
  14  
  15  //go:embed artofwar.txt
  16  var artOfWarText string
  17  
  18  // Category identifies one of the 13 strategic domains from The Art of War.
  19  type Category uint8
  20  
  21  const (
  22  	CatAssessment   Category = iota // I: Laying Plans
  23  	CatResources                    // II: Waging War
  24  	CatStratagem                    // III: Attack by Stratagem
  25  	CatPositioning                  // IV: Tactical Dispositions
  26  	CatMomentum                     // V: Energy
  27  	CatExploitation                 // VI: Weak Points and Strong
  28  	CatAdaptation                   // VII: Maneuvering
  29  	CatFlexibility                  // VIII: Variation of Tactics
  30  	CatSignalReading                // IX: The Army on the March
  31  	CatEnvironment                  // X: Terrain
  32  	CatUrgency                      // XI: The Nine Situations
  33  	CatForce                        // XII: The Attack by Fire
  34  	CatIntelligence                 // XIII: The Use of Spies
  35  )
  36  
  37  var categoryNames = [13]string{
  38  	"assessment", "resources", "stratagem", "positioning",
  39  	"momentum", "exploitation", "adaptation", "flexibility",
  40  	"signal-reading", "environment", "urgency", "force", "intelligence",
  41  }
  42  
  43  func (c Category) String() string {
  44  	if int(c) < len(categoryNames) {
  45  		return categoryNames[c]
  46  	}
  47  	return "unknown"
  48  }
  49  
  50  // Level distinguishes element granularity within a chapter.
  51  type Level uint8
  52  
  53  const (
  54  	LevelChapter   Level = iota // Whole chapter summary
  55  	LevelPrinciple              // Core principle (paragraph)
  56  	LevelMaxim                  // Single maxim (sentence)
  57  	LevelTactic                 // Tactical fragment (phrase)
  58  )
  59  
  60  var levelNames = [4]string{"chapter", "principle", "maxim", "tactic"}
  61  
  62  func (l Level) String() string {
  63  	if int(l) < len(levelNames) {
  64  		return levelNames[l]
  65  	}
  66  	return "unknown"
  67  }
  68  
  69  // Maxim is a single strategic principle extracted from a chapter.
  70  type Maxim struct {
  71  	Text    string
  72  	Chapter int
  73  	Tags    []string
  74  }
  75  
  76  // Chapter holds one of the 13 chapters of The Art of War.
  77  type Chapter struct {
  78  	Number   int
  79  	Title    string
  80  	Category Category
  81  	Text     string  // Full text of the chapter
  82  	Maxims   []Maxim // Extracted key principles
  83  }
  84  
  85  var chapters [13]Chapter
  86  
  87  // chapterDefs maps chapter markers to their metadata.
  88  var chapterDefs = [13]struct {
  89  	marker   string
  90  	title    string
  91  	category Category
  92  }{
  93  	{"## I.", "Laying Plans", CatAssessment},
  94  	{"## II.", "Waging War", CatResources},
  95  	{"## III.", "Attack by Stratagem", CatStratagem},
  96  	{"## IV.", "Tactical Dispositions", CatPositioning},
  97  	{"## V.", "Energy", CatMomentum},
  98  	{"## VI.", "Weak Points and Strong", CatExploitation},
  99  	{"## VII.", "Maneuvering", CatAdaptation},
 100  	{"## VIII.", "Variation of Tactics", CatFlexibility},
 101  	{"## IX.", "The Army on the March", CatSignalReading},
 102  	{"## X.", "Terrain", CatEnvironment},
 103  	{"## XI.", "The Nine Situations", CatUrgency},
 104  	{"## XII.", "The Attack by Fire", CatForce},
 105  	{"## XIII.", "The Use of Spies", CatIntelligence},
 106  }
 107  
 108  func init() {
 109  	parseChapters()
 110  }
 111  
 112  // parseChapters splits the embedded text into 13 chapters and extracts maxims.
 113  func parseChapters() {
 114  	lines := strings.Split(artOfWarText, "\n")
 115  
 116  	// Find chapter boundaries.
 117  	type boundary struct {
 118  		line int
 119  		idx  int // index into chapterDefs
 120  	}
 121  	var bounds []boundary
 122  	for i, line := range lines {
 123  		for j, def := range chapterDefs {
 124  			if strings.HasPrefix(line, def.marker) {
 125  				bounds = append(bounds, boundary{i, j})
 126  			}
 127  		}
 128  	}
 129  
 130  	for bi, b := range bounds {
 131  		end := len(lines)
 132  		if bi+1 < len(bounds) {
 133  			end = bounds[bi+1].line
 134  		}
 135  
 136  		// Chapter text is everything after the heading line.
 137  		var textLines []string
 138  		for _, l := range lines[b.line+1 : end] {
 139  			textLines = append(textLines, l)
 140  		}
 141  		text := strings.TrimSpace(strings.Join(textLines, "\n"))
 142  
 143  		def := chapterDefs[b.idx]
 144  		chapters[b.idx] = Chapter{
 145  			Number:   b.idx + 1,
 146  			Title:    def.title,
 147  			Category: def.category,
 148  			Text:     text,
 149  			Maxims:   extractMaxims(text, b.idx+1),
 150  		}
 151  	}
 152  }
 153  
 154  // extractMaxims identifies imperative and declarative maxims from chapter text.
 155  func extractMaxims(text string, chapter int) []Maxim {
 156  	var maxims []Maxim
 157  
 158  	// Split into sentences.
 159  	sentences := splitSentences(text)
 160  	for _, s := range sentences {
 161  		s = strings.TrimSpace(s)
 162  		if len(s) < 20 {
 163  			continue
 164  		}
 165  		if isMaxim(s) {
 166  			maxims = append(maxims, Maxim{
 167  				Text:    s,
 168  				Chapter: chapter,
 169  				Tags:    maximTags(s),
 170  			})
 171  		}
 172  	}
 173  	return maxims
 174  }
 175  
 176  // splitSentences breaks text into sentences on period, exclamation, or
 177  // question mark boundaries that are followed by whitespace or end of text.
 178  func splitSentences(text string) []string {
 179  	var sentences []string
 180  	var current strings.Builder
 181  
 182  	runes := []rune(text)
 183  	for i, r := range runes {
 184  		current.WriteRune(r)
 185  		if (r == '.' || r == '!' || r == '?') && (i+1 >= len(runes) || runes[i+1] == ' ' || runes[i+1] == '\n') {
 186  			s := strings.TrimSpace(current.String())
 187  			if s != "" {
 188  				sentences = append(sentences, s)
 189  			}
 190  			current.Reset()
 191  		}
 192  	}
 193  	if s := strings.TrimSpace(current.String()); s != "" {
 194  		sentences = append(sentences, s)
 195  	}
 196  	return sentences
 197  }
 198  
 199  // isMaxim returns true if a sentence looks like a strategic principle.
 200  func isMaxim(s string) bool {
 201  	lower := strings.ToLower(s)
 202  
 203  	// Imperative indicators.
 204  	imperatives := []string{
 205  		"must", "should", "therefore", "hence", "thus",
 206  		"he who", "the general", "the skilful", "the skillful",
 207  		"if you", "if your", "if he", "when you", "when the",
 208  		"do not", "never", "always", "let ",
 209  		"all warfare", "the art of war", "supreme excellence",
 210  		"in war", "in battle",
 211  	}
 212  	for _, kw := range imperatives {
 213  		if strings.Contains(lower, kw) {
 214  			return true
 215  		}
 216  	}
 217  
 218  	// Short declarative sentences are often maxims.
 219  	if len(s) < 120 && !strings.Contains(s, "Sun Tz") {
 220  		words := strings.Fields(s)
 221  		if len(words) >= 4 && len(words) <= 25 {
 222  			return true
 223  		}
 224  	}
 225  
 226  	return false
 227  }
 228  
 229  // maximTags returns lattice-relevant tags for a maxim.
 230  func maximTags(s string) []string {
 231  	lower := strings.ToLower(s)
 232  	var tags []string
 233  
 234  	tagKeywords := map[string][]string{
 235  		"deception":   {"decep", "feign", "pretend", "appear", "seem"},
 236  		"speed":       {"swift", "rapid", "quick", "haste", "speed"},
 237  		"patience":    {"wait", "patient", "timing", "moment"},
 238  		"terrain":     {"ground", "terrain", "position", "height"},
 239  		"intelligence": {"spy", "spies", "knowledge", "foreknowledge", "know"},
 240  		"resources":   {"supply", "provision", "cost", "expenditure"},
 241  		"morale":      {"spirit", "morale", "courage", "heart"},
 242  		"discipline":  {"discipline", "order", "command", "obey"},
 243  		"adaptation":  {"adapt", "change", "vary", "flexible", "modify"},
 244  		"force":       {"attack", "strike", "fight", "battle", "force"},
 245  	}
 246  
 247  	for tag, keywords := range tagKeywords {
 248  		for _, kw := range keywords {
 249  			if strings.Contains(lower, kw) {
 250  				tags = append(tags, tag)
 251  				break
 252  			}
 253  		}
 254  	}
 255  	return tags
 256  }
 257  
 258  // Chapters returns all 13 chapters.
 259  func Chapters() [13]Chapter {
 260  	return chapters
 261  }
 262  
 263  // ChapterByNumber returns a chapter (1-13) or empty if out of range.
 264  func ChapterByNumber(n int) Chapter {
 265  	if n < 1 || n > 13 {
 266  		return Chapter{}
 267  	}
 268  	return chapters[n-1]
 269  }
 270