Explain generic types and add DebugLevel to Player
This simulation engine is intended for people who are interested in game design, not computer programming -- the engine wants to do all the engine stuff so the simulation can be implemented with less familiarity with the language. Generics, however, are not widely regarded as a "new programmer" thing -- even though they're surprisingly familiar, in the end (slice-of-T, map-from-K-to-V). So a long comment explaining a bit about what's going on seems warranted.
This commit is contained in:
		| @@ -2,7 +2,55 @@ package cardsim | ||||
|  | ||||
| import "math/rand" | ||||
|  | ||||
| // Player stores all gameplay state for one player. | ||||
| // Player stores all gameplay state for one player at a specific point in time. | ||||
| // Game-specific data is stored in Stats. | ||||
| // | ||||
| // Player is a generic type -- see https://go.dev/blog/intro-generics for more | ||||
| // information on how these work. Think of "Player" as a "type of type" -- | ||||
| // when you create one, you tell it what kind of data it needs to keep for | ||||
| // the simulation itself, and each Player that works with a different kind of | ||||
| // data is a different kind of Player and the compiler will help you with that. | ||||
| // This is the same idea as "slice of something" or "map from something to | ||||
| // something" -- different kinds of Players are different from each other and | ||||
| // "know" what type of data they use, so the compiler can tell you if you're | ||||
| // using the wrong type. | ||||
| // | ||||
| // Generic types have to use a placeholder to represent the type (or types -- | ||||
| // consider maps, which have both keys and values) that will be more specific | ||||
| // when the type is actually used. They're called "type parameters", like | ||||
| // function parameters, because they're the same kind of idea. A function puts | ||||
| // its parameters into variables so you can write a function that works with | ||||
| // whatever data it gets; a generic type takes type parameters and represents | ||||
| // them with type placeholders so you can write a *type* that works with | ||||
| // whatever specific other types it gets. | ||||
| // | ||||
| // Just like function parameters have a type that says what kind of data the | ||||
| // function works with, type parameters have a "type constraint" that says what | ||||
| // kind of types the generic type works with. Go already has a familiar way | ||||
| // to express the idea of "what a type has to do": `interface`. In Go, type | ||||
| // constraints are just interfaces. | ||||
| // | ||||
| // But wait, why use generics at all? Can't we just use an interface in the | ||||
| // normal way instead of doing this thing? Well, yes, we could, but then the | ||||
| // compiler doesn't know that the "real types" for things matching these | ||||
| // interfaces all have to actually be the same type. The compiler will stop | ||||
| // you from putting an `Orange` into a `[]Apple`, but it wouldn't stop you from | ||||
| // putting a `Fruit` into a `[]Fruit` because, well, of course it wouldn't, | ||||
| // they're the same type. | ||||
| // | ||||
| // Different simulation games made with `cardsim` are different. Rules made for | ||||
| // simulating the economy of a kobold colony and mine wouldn't work at all with | ||||
| // data for a simulation about three flocks of otter-gryphons having a | ||||
| // territory conflict over a river full of fish. By using generics, the compiler | ||||
| // can recognize functions and data and types intended for different simulation | ||||
| // games and prevent you from using the wrong one, when it wouldn't be able to | ||||
| // if all this stuff was written for "some simulation game, don't care what". | ||||
| // | ||||
| // Generic interfaces (like `Card[C]`, `Rule[C]`, `InfoPanel[C]`, and more) | ||||
| // don't mean you have to write generics of your own. It's exactly the opposite! | ||||
| // Because the interface has this extra type in it, you only need to implement | ||||
| // the specific kind of interface that works with your game. There's more detail | ||||
| // on this in the comment on `Rule[C]`. | ||||
| type Player[C StatsCollection] struct { | ||||
| 	Stats             C | ||||
| 	Name              string | ||||
| @@ -19,4 +67,6 @@ type Player[C StatsCollection] struct { | ||||
| 	Turn              int | ||||
| 	TemporaryMessages []Message | ||||
| 	TemporaryPanels   []InfoPanel[C] | ||||
|  | ||||
| 	DebugLevel int | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user