package cardsim import "math/rand" // 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 Deck *Deck[C] Hand []Card[C] HandLimit int ActionsPerTurn int ActionsRemaining int PermanentActions []Card[C] InfoPanels []InfoPanel[C] Prompt InfoPanel[C] Rules *RuleCollection[C] Rand rand.Rand Turn int TemporaryMessages []Message TemporaryPanels []InfoPanel[C] DebugLevel int }