// Polypods //--------------------------------------------- // Utility for determining if a group is alive (define "IsAliveAt" // (<= 0 (+ (results from:#1 to:(from) (if (!= 1 (state at:(to))) 1 (- 1)) ) ) ) ) //---------------------------------- // Capture Locations and procedure (define "CaptureSites" // i.e. opponent foot occupied sites that are next to the body sites of the group that includes the last placement (intersection (sites Around (intersection (sites Group at:(last To)) (sites State 1))) (difference (sites Occupied by:Next) (sites State 1)) ) ) (define "KeySitesOfAffectedGroups" (sites (results from:"CaptureSites" to:(min (array (difference (sites Group at:(from)) "CaptureSites"))) (to) ) ) ) (define "AffectedGroupAt" // The updated group without captured terminals associated with the key location. (difference (sites Group at:#1) (sites (values Remembered "CS"))) ) (define "Check4IllegalCapture" (and { ("UpdateTerminals" (sites Group at:(var "ConvertedFootSite"))) } (then (if //work around: using (last To) did not pick up the last consequence, but rather the last move (not ("IsAliveAt" (sites Group at:(var "ConvertedFootSite")))) (trigger "IllegalCapture" Mover) ) ) ) ) (define "ClaimCapture" // site (if (= 1 (state at:(site))) (remove (site)) (add (to (site) (apply (and (set Var "ConvertedFootSite" (to)) (remove (to)) ) ) ) (then ("Check4IllegalCapture")) ) ) ) //------------------------------- (define "Captures" (do (forEach Site "CaptureSites" (remember Value "CS" (site) unique:True) (then (forEach Site "KeySitesOfAffectedGroups" (remember Value "KS" (site) unique:True) ) ) ) next:(forEach Site (sites (values Remembered "CS")) (remove (site)) (then (forEach Value (values Remembered "KS") // a key site within the remainder of the group ("UpdateTerminals" ("AffectedGroupAt" (value))) ) ) ) (then (and (forEach Value (values Remembered "KS") (if (not ("IsAliveAt" ("AffectedGroupAt" (value)))) (forEach Site ("AffectedGroupAt" (value)) ("ClaimCapture") ) ) ) (forget Value "CS" All (then (forget Value "KS" All)) ) ) ) ) ) //--------------------------------------------- // Updating Group structures (define "SurroundingSitesInGroup" (array (intersection #1 (sites Around (site)))) ) (define "UpdateTerminals" (if (= 6 (count Sites in:(sites Corners))) ("UpdateTerminalsHex" #1) ("UpdateTerminalsSquareOriented" #1) )) (define "UpdateTerminalsHex" // such as (sites) or (sites Group at:(last To)) (forEach Site #1 (if (or (< 2 (size Array ("SurroundingSitesInGroup" #1))) (and (= 2 (size Array ("SurroundingSitesInGroup" #1))) (< 1 (count Steps (min ("SurroundingSitesInGroup" #1)) (max ("SurroundingSitesInGroup" #1)) ) ) ) ) (set State at:(site) 1) (set State at:(site) 0) ) ) ) (define "UpdateTerminalsSquareOriented" // such as (sites) or (sites Group at:(last To)) (forEach Site #1 (if (< 1 (size Array ("SurroundingSitesInGroup" #1))) (set State at:(site) 1) (if (is Within (what at:(site)) in:(sites Around (site) N)) (set State at:(site) 2) (if (is Within (what at:(site)) in:(sites Around (site) E)) (set State at:(site) 3) (if (is Within (what at:(site)) in:(sites Around (site) S)) (set State at:(site) 4) (if (is Within (what at:(site)) in:(sites Around (site) W)) (set State at:(site) 5) (set State at:(site) 0) ) ) ) ) ) ) ) //-------------------------------- // Placement (define "ToAllowedSites" (to (difference (sites Empty) (sites Around (intersection (sites Occupied by:Next) (sites State 1)) ) ) // level:0 ) ) //----------------------------------------------- // Main routine (game "Polypods" (players 2) (equipment { (board use:Vertex) (piece "Disc" Each maxState:1 maxValue:4) }) (rules (play (or (move Pass) (do (do (move Add "ToAllowedSites" (then (and (set Value at:(last To) (layer of:(last To))) ("UpdateTerminals" (sites Group at:(last To))) ) ) ) ifAfterwards:("IsAliveAt" (sites Group at:(last To))) (then ("Captures")) ) ifAfterwards:(not (is Triggered "IllegalCapture" Mover)) ) (then ("Score") ) ) ) (end (if (all Passed) { (if (> (score Mover) (score Next)) (result Mover Win) ) (if (and (= (score Mover) (score Next)) (>= ("FringeScoreOf" Mover) ("FringeScoreOf" Next)) ) (result Mover Win) ) (if (and (= (score Mover) (score Next)) (= ("FringeScoreOf" Mover) ("FringeScoreOf" Next)) ) (result Mover Draw) ) } (result Next Win) ) ) ) ) //----------------------------- //Scoring (define "Score" (and ("ScoreCore4" Mover) ("ScoreCore4" Next) )) (define "ScoreFringe4" (set Score #1 ("FringeScoreOf" #1))) (define "ScoreCore4" (set Score #1 (count Sites in:(intersection (sites Occupied by:#1) (sites State 1) ) ) ) ) (define "FringeScoreOf" (count Sites in:(difference (sites Around (sites Occupied by:#1)) (sites Around (difference (sites Occupied by:All) (sites Occupied by:#1)) includeSelf:True) ) ) ) //------------------------------------------------- // Options (option "Board" args:{ } { (item "Square 4-7" <(rectangle 4 7)> <("Graphics")> "Order 5 Square Grid")* (item "Square 5-6" <(rectangle 5 6)> <("Graphics")> "Order 5 Square Grid") (item "Square 5-7" <(rectangle 5 7)> <("Graphics")> "Order 5 Square Grid") (item "Square 6" <(square 6)> <("Graphics")> "Order 6 Square Grid") (item "Square 8**" <(square 8)> <("Graphics")> "Order 8 Square Grid")** (item "Square 10**" <(square 10)> <("Graphics")> "Order 10 Square Grid")* (item "Square 12" <(square 12)> <("Graphics")> "Order 12 Square Grid") (item "Square 14" <(square 14)> <("Graphics")> "Order 14 Square Grid") (item "Square 16" <(square 16)> <("Graphics")> "Order 16 Square Grid") (item "Square 18" <(square 18)> <("Graphics")> "Order 18 Square Grid") (item "Hex 3" <(tri Hexagon 3)> <("GraphicsOption" "counter.svg" 0.98 "disc.svg" 0.75)> "Order 3, Hex Grid") (item "Hex 3,4*" <(tri Limping 3)> <("GraphicsOption" "counter.svg" 0.98 "disc.svg" 0.75)> "Order 3-4, Hex Grid")* (item "Hex 4" <(tri Hexagon 4)> <("GraphicsOption" "counter.svg" 0.98 "disc.svg" 0.75)> "Order 4, Hex Grid") (item "Hex 4,5**" <(tri Limping 4)> <("GraphicsOption" "counter.svg" 0.98 "disc.svg" 0.75)> "Order 4-5, Hex Grid")** (item "Hex 5" <(tri Hexagon 5)> <("GraphicsOption" "counter.svg" 0.98 "disc.svg" 0.75)> "Order 5, Hex Grid") (item "Hex 5,6" <(tri Limping 5)> <("GraphicsOption" "counter.svg" 0.98 "disc.svg" 0.75)> "Order 5-6, Hex Grid") // (item "3D-4" <"ThreeD4Board"> <("Graphics3D" "disc.svg" "square.svg")> "3D Grid") // (item "3D" <"ThreeD456Board"> <("Graphics3D" "disc.svg" "square.svg")> "3D Grid") } ) (define "ThreeD456Board" (layers 4 (rectangle 5 6))) // (scale 1.8 1 5 (layers 4 (rectangle 5 6))) // (skew .7 (scale 1.6 .2 (layers 4 (rectangle 5 6)))) // (layers 4 (skew .7 (scale 1.8 1 (rectangle 5 6)))) //) (define "ThreeD4Board" (layers 4 (rectangle 4 4))) // (scale 1.8 1 5 (layers 4 (rectangle 4 4))) (define "Graphics" ("GraphicsOption" "counter.svg" 0.98 "disc.svg" 0.75)) // Other possible styles // "Stub Feet" // ("GraphicsOption" "square.svg" 0.9 "senetpiece" 0.9) // "Feet with toes" // ("GraphicsOption" "square.svg" 0.9 "queen_symbola" 0.9) // "Small feet"// ("GraphicsOption" "counter.svg" 0.98 "disc.svg" 0.75) // "Dots" // ("GraphicsOption" "square.svg" 0.9 "disc.svg" 0.7) // "3D" // ("Graphics3D" "disc.svg" "square.svg") (define "P1Colour" (colour DarkGreen)) (define "P2Colour" (colour White)) // (define "P2Colour" (colour 230 224 200)) (define "GraphicsOption" // Orients symbols used for feet on the square grid - (graphics { (player Colour P1 (colour DarkGreen)) (player Colour P2 (colour Cream)) (board Style Graph) (board Background fillColour:(colour 150 120 30 90) edgeColour:(colour 150 120 30 90) ) (board StyleThickness InnerEdges .2) (board StyleThickness OuterEdges .2) (board StyleThickness InnerVertices .5) (no Sunken False) (show Edges Diagonal Hidden) (piece Scale .04) (piece Foreground "Disc1" state:0 image:#3 fillColour:(colour DarkGreen) scale:#4 rotation:0 ) (piece Background "Disc1" state:1 image:#1 fillColour:(colour DarkGreen) scale:#2 rotation:90 ) (piece Foreground "Disc1" state:2 image:#3 fillColour:(colour DarkGreen) scale:#4 rotation:180 ) (piece Foreground "Disc1" state:3 image:#3 fillColour:(colour DarkGreen) scale:#4 rotation:270 ) (piece Foreground "Disc1" state:4 image:#3 fillColour:(colour DarkGreen) scale:#4 rotation:0 ) (piece Foreground "Disc1" state:5 image:#3 fillColour:(colour DarkGreen) scale:#4 rotation:90 ) (piece Foreground "Disc2" state:0 image:#3 fillColour:"P2Colour" scale:#4 rotation:0 ) (piece Background "Disc2" state:1 image:#1 fillColour:"P2Colour" scale:#2 rotation:90 ) (piece Foreground "Disc2" state:2 image:#3 fillColour:"P2Colour" scale:#4 rotation:180 ) (piece Foreground "Disc2" state:3 image:#3 fillColour:"P2Colour" scale:#4 rotation:270 ) (piece Foreground "Disc2" state:4 image:#3 fillColour:"P2Colour" scale:#4 rotation:0 ) (piece Foreground "Disc2" state:5 image:#3 fillColour:"P2Colour" scale:#4 rotation:90 ) //(show Piece State) (show Symbol "X" (forEach (sites Around (sites Occupied by:All) if:(is Empty (to))) if:(and { (all Passed) (no Pieces P2 in:(sites Around (site))) } )) edgeColour:"P1Colour" scale:.4 ) (show Symbol "X" (forEach (sites Around (sites Occupied by:All) if:(is Empty (to))) if:(and { (all Passed) (no Pieces P1 in:(sites Around (site))) } )) edgeColour:"P2Colour" scale:.4 ) } )) (define "Graphics3D" // Future use - Game works, but Ludii Gui doesn't (graphics { (player Colour P1 (colour DarkGreen)) (player Colour P2 (colour Cream)) (board Style Graph) (board StyleThickness InnerEdges .2) (board StyleThickness OuterEdges .2) (board StyleThickness InnerVertices .5) (show Edges Diagonal Hidden) // (piece Scale .04) //Probably should use partly transparent colors for 3D (piece Colour fillColour:(colour 0 0 0 0) strokeColour:(colour 0 0 0 0)) (piece Foreground "Disc1" state:0 value:0 image:#1 fillColour:(colour DarkGreen) scale:1 rotation:0) (piece Foreground "Disc1" state:0 value:1 image:#1 fillColour:(colour DarkGreen) scale:.8 rotation:0) (piece Foreground "Disc1" state:0 value:2 image:#1 fillColour:(colour DarkGreen) scale:.6 rotation:0) (piece Foreground "Disc1" state:0 value:3 image:#1 fillColour:(colour DarkGreen) scale:.4 rotation:0) (piece Foreground "Disc1" state:0 value:4 image:#1 fillColour:(colour DarkGreen) scale:.2 rotation:0) (piece Foreground "Disc2" state:0 value:0 image:#1 fillColour:(colour Cream) scale:1 rotation:0) (piece Foreground "Disc2" state:0 value:1 image:#1 fillColour:(colour Cream) scale:.8 rotation:0) (piece Foreground "Disc2" state:0 value:2 image:#1 fillColour:(colour Cream) scale:.6 rotation:0) (piece Foreground "Disc2" state:0 value:3 image:#1 fillColour:(colour Cream) scale:.4 rotation:0) (piece Background "Disc2" state:0 value:4 image:#1 fillColour:(colour Cream) scale:.2 rotation:0) (piece Background "Disc1" state:1 value:0 image:#2 fillColour:(colour DarkGreen) scale:1 rotation:0) (piece Background "Disc1" state:1 value:1 image:#2 fillColour:(colour DarkGreen) scale:.8 rotation:0) (piece Background "Disc1" state:1 value:2 image:#2 fillColour:(colour DarkGreen) scale:.6 rotation:0) (piece Background "Disc1" state:1 value:3 image:#2 fillColour:(colour DarkGreen) scale:.4 rotation:0) (piece Background "Disc1" state:1 value:4 image:#2 fillColour:(colour DarkGreen) scale:.2 rotation:0) (piece Background "Disc2" state:1 value:0 image:#2 fillColour:(colour Cream) scale:1 rotation:0) (piece Background "Disc2" state:1 value:1 image:#2 fillColour:(colour Cream) scale:.8 rotation:0) (piece Background "Disc2" state:1 value:2 image:#2 fillColour:(colour Cream) scale:.6 rotation:0) (piece Background "Disc2" state:1 value:3 image:#2 fillColour:(colour Cream) scale:.4 rotation:0) (piece Background "Disc2" state:1 value:4 image:#2 fillColour:(colour Cream) scale:.2 rotation:0) // (piece Scale scaleX:1.5 scaleY:.95) // (show Piece Value) } )) //--------------------------------------------- (metadata (info { (description "Polypods was created as an exploration of a new capture concept in which a group lives as long as it has more terminal ends than core pieces. Group capture naturally became determined in two stages: removing terminals, which then in turn cause the group to exceed its core limit. This works best on the hex grid. Since terminals are added in play, they can also be removed without destroying the group. All groups fall in the range of with 0 to 3 free terminals. Additions can either help stabilize the group, or destabilize it. Efficiency in scoring encourages growing groups to near the critical size. As does attacking another player's terminals, and being attacked. Critical size groups are most vulnerable to being killed by terminal attacks instead of merely suffering a terminal loss. This is not difficult to use as a tactic, and if the result were to clear too much of the board the game would be too unstable a cycle. Removing only the core, ensures that the resultant groups will be singletons and pairs which are all legal formations, and keep the board from clearing too much. However, tit-for-tat captures still can cause major resets. Therefore, the capture rule specifies that the ownership of the terminals of the captured groups also changes to the capturing player. This final solution allows tactical instability, but strategic stability. On small boards ties may be more frequent than some players would like. Thus, a tie-breaker based on empty territory control has also been added. This value is intuitive, and not frequently correlated to the core score, so it functions well in this role. Group count is not used for scoring, because the terminals provide a benefit in allowing for stability and future growth, and thus function best as investments rather than outcomes.") (rules "Polypods is a game about groups of connected stones called Polypods. Understanding the mythical life of a polypod should help you to easily remember the rules. Polypods are creatures that have poisonous body segments that tie together its numerous feet. To survive, at least half of a polypod must be its feet, and this determines how they may grow and the shapes they may take. The feet die and fall off upon contact with the poisonous body segments of other polypods, however, since the feet themselves are not poisonous, there is no harm in their contact with other polypod's feet. Fortunately a polypod's foot is able to smell the area around it, and thus a polypod avoids extending its feet to touch the bodies of competing polypods. Hungry polypods do, however, find ways to bring their body against a foot of another polypod, causing that foot (or feet) to self-amputate. If the attacking polypod is lucky, the loss of a foot will cause its neighbor to die. The attacking polypod then ingests the body of its dead neighbor and spawns new polypods in the dead neighbor's feet. Definitions: -- A POLYPOD is a group of interconnected stones. The group includes every stone of the same color that is connected to it. (A single stone, or monopod, is also considered as a polypod in these rules.) Each stone in a polypod has one of two roles: Body or foot. -- FOOT STONES (FEET) are stones that connect to at most two other friendly stones:- and if connected to two stones, those two must already be adjacent to each other. -- BODY STONES are those that serve to link all the polypod feet together. As the polypods grow or shrink, the roles of their individual stones change accordingly. (In this application the body stones are marked with squares for convenience in reading the board.) A polypod is ALIVE if it has at least as many feet as body stones. -- Foot stones in contact with a body stone of the opposite color SELF-AMPUTATE (ie get immediately removed from the board) -- when a Polypod DIES, its body stones are removed, and its feet change ownership. Rules: The game uses discs played on the intersections of a triangular grid of the desired size and shape. The board starts empty and Dark starts. Turns Alternate. The moving player, either passes, or does the following sequence of actions, if possible: 1. The mover places a piece on an empty space. -- The placement must not cause the immediate self-amputation of the placed stone. -- It may add to or merge friendy polypods, UNLESS this would cause those polypod(s) to die. 2. The mover removes all self-amputating feet. 3. The mover resolves all polypod deaths. The game ends when the players pass consecutively. The winner is determined by tallying the value of each player's polypods. The value of a polypod is the number of body stones that it contains. If tied, score all the empty spaces adjacent to the players' own polypods. If this count is also equal, the game is considered a draw.") (id "1994") (version "1.3.12") (classification "experimental") (author "Dale W. Walton") (credit "Dale W. Walton") (date "06-05-2022") } ) (ai "Polypods_ai" ) )