//-------------------------------------- // Problems: even after complicated optimizations (which helped a lot) // the script is still very heavy on the AI. // But at least human play is not slowed down too much. // Observations: - // full moves show that everything other than placement of a piece on a cell is done twice. // maybe this is also slowing the script down. // Sometimes the locations of the second edge marker // and the size of the indecise and move markers changes // a few seconds after the move is displayed. //--------------------------------------- // Direction to integer Utility (define "D" // from, to (if (or (= #1 (ahead Cell #2 W)) (= #1 (ahead Cell #2 E)) ) 0 (if (or (= #1 (ahead Cell #2 WSW)) (= #1 (ahead Cell #2 ENE)) ) 1 (if (or (= #1 (ahead Cell #2 SSW)) (= #1 (ahead Cell #2 NNE)) ) 2 (if (or (= #1 (ahead Cell #2 S)) (= #1 (ahead Cell #2 N)) ) 3 (if (or (= #1 (ahead Cell #2 SSE)) (= #1 (ahead Cell #2 NNW)) ) 4 5 ) ) ) ) ) ) //-------------------------- // Diagonal processing (cosmetic, but essential for the players to understand the game) (define "HalfWayEdge" (regionSite (intersection (sites Incident Edge of:Cell at:(regionSite ("SharedCells" #1 #2) index:0)) (sites Incident Edge of:Cell at:(regionSite ("SharedCells" #1 #2) index:1)) ) index:0 ) ) (define "SharedCells" (intersection (sites Around Cell #1 Orthogonal) (sites Around Cell #2 Orthogonal) ) ) (define "AddDiagonalMarkers" (forEach Site (sites Around Cell (var "SitePlaced") Diagonal if:(and (= (id "Ball" Mover) (what at:(to))) (not (is Within (id "Ball" Mover) in:(intersection (sites Around Cell (var "SitePlaced") Orthogonal) (sites Around Cell (to) Orthogonal) ) ) ) ) ) (and (add (piece (id "I" Mover)) (to Edge ("HalfWayEdge" (var "SitePlaced") (site))) stack:True ) (set State Edge at:("HalfWayEdge" (var "SitePlaced") (site)) ("D" (var "SitePlaced") (site))) ) ) ) (define "RemoveExcessDiagonals" (forEach Site (intersection (sites Incident Edge of:Cell at:(var "SitePlaced")) (sites Occupied by:Mover component:"I" on:Edge) ) (remove Edge (site)) ) ) //------------------------------- // Connectivity and Liberty determinations by site (define "DiagonalBoundary" // One test in NotBlockedBy (= 2 (count Sites Cell in:(intersection { (sites Occupied by:#1 component:"Ball" on:Cell) (sites Around Cell (from) Orthogonal) (sites Around Cell (to) Orthogonal) }) ) ) ) (define "NotBlockedBy" //for continuity testing in stepwise connections (and { (= -1 (where Level (id "Ball" #1) Cell at:(to))) (= -1 (where Level (id "I" #1) Cell at:(to))) (not ("DiagonalBoundary" #1)) }) ) (define "IsNotBlockedByBetween" //doesn't work for broken markers (!= Infinity (count Steps (step Orthogonal (to if:("NotBlockedBy" #1) // if:(or (= (to) (var "SitePlaced")) ("NotBlockedBy" #1)) ) ) #2 #3 ) ) ) // Allow state to be calculated as (2 - liberties) // State 0: liberties > 1 OK, State 1 = liberties = 1, State 2: Liberties = 0 Dead // State 1 is used identify suicidal groups and cells to filter the Add moves. (define "Liberties" (max 0 (min 2 (- (+ (if (is In #3 (sites Outer)) 1 0) (count Sites in:(intersection (sites Outer) (sites Distance (step Orthogonal (to if:("NotBlockedBy" #2))) from:#3 (min 1) ) ) ) ) (+ (if (= (id "Ball" #1) (what at:#3)) 1 0) (count Pieces #1 "Ball" in:(sites Distance (step Orthogonal (to if:("NotBlockedBy" #2))) from:#3 (min 1) ) ) ) ) ) ) ) //------------------------------ // Suicidal site determination //-- suicidal sites cannot be added to, and are rarely reset during the game, so remembering them speeds up move processing considerably. // Liberties at an opponent's bridging markers need to be counted explicitly //--------------------------------------- //---------------------------------- // Update own states after move (define "UpdateListOrStatePlayerBlocker" // note parameter-shift (if (= (id "Ball" #3) (what at:(site))) (and (set State at:(site) #2) (if (= 2 #2) (trigger "Enmeshed" Next) )) (if (or (> 0 (where Level (id "I" #4) Cell at:(site))) // no mover marker (>= 1 ("Liberties" #3 #4 (site))) ) (remember Value #1 (site) unique:True) ) ) ) //-------------------------------------------------- // Find lists of sites to update // ("UpdateListStateAroundPlayerBlocker" #1 #2 (value) Next Mover) // ("UpdateListStateAroundPlayerBlocker" "SuicideP1" 1 (var "SitePlaced") Mover Next) (define "UpdateListStateAroundPlayerBlocker" (if (= 0 #2) //sites to allow (Forget) (and { (remember Value "Update" #2) (forEach Site (sites Occupied by:#4 component:"Ball") (if (and ("IsNotBlockedByBetween" #5 (site) #3 ) (not (= (state at:(site)) #2)) ) (set State at:(site) #2) ) ) (forEach Site (sites (values Remembered #1)) (apply if:(and ("IsNotBlockedByBetween" #5 (site) #3 ) (or (>= 0 (where Level (id "I" #5) Cell at:(site))) (< 1 ("Liberties" #4 #5 (site))) ) ) (forget Value #1 (site)) ) ) }) //sites to restrict (remember) (and (forEach Site (sites To (select (from #3))) ("UpdateListOrStatePlayerBlocker" #1 #2 #4 #5) ) (forEach Site (difference (difference (sites Board) (sites Occupied by:#5 component:"Ball") ) (sites (values Remembered #1)) ) (if ("IsNotBlockedByBetween" #5 (site) #3) ("UpdateListOrStatePlayerBlocker" #1 #2 #4 #5) ) ) ) ) ) (define "UpdateOpponentsState" (forEach Value (array (sites Around (var "SitePlaced") All // to pick up special case where piece lies between two new markers. )) (if (and { (not (= (id "Ball" Mover) (what at:(value)))) (not (is In (value) (values Remembered #1))) (= (- 2 #2) ("Liberties" Next Mover (value))) }) ("UpdateListStateAroundPlayerBlocker" #1 #2 (value) Next Mover) ) ) ) //---------------------------------------------------------- //----------------------- // Orthogonal marking affects connectivity // and is needed before checking for win (define "AddOrthogonalMarkers" // bug adds 2 of each marker (custodial (from (var "SitePlaced")) Orthogonal (between (exact 1) if:(and { (!= (id "Ball" Mover) (what at:(between))) (!= (id "Ball" Next) (what at:(between))) (= 2 (count Sites Cell in:(intersection (sites Around (between) Orthogonal) (sites Occupied by:Mover component:"Ball") ) ) ) }) (apply (and (add (piece (id "I" Mover)) (to (between) ) stack:True ) (set State Cell at:(between) level:(level) ("D" (var "SitePlaced") (between))) ) ) ) (to if:(= (id "Ball" Mover) (what at:(to)))) ) ) (define "RemoveExcessOrthogonals" (forEach Site (sites Around Cell (var "SitePlaced") Orthogonal if:(and (!= -1 (where Level (id "I" Mover) Cell at:(to))) (!= 2 (count Sites Cell in:(intersection (sites Around Cell (to) Orthogonal) (sites Occupied by:Mover component:"Ball" on:Cell) ) ) ) ) ) (and (remove Cell (site) level:(where Level (id "I" Mover) Cell at:(site))) (if //workaround (= 2 ("Liberties" Next Mover (site))) (forget Value #1 (site)) (remember Value #1 (site) unique:True) ) ) ) ) //--------------------------- // Main move (define "AddAtEmptyExcept" (move Add (piece (id "Ball" Mover)) (to (difference (sites Empty) (sites (values Remembered #1)) ) (apply (set Var "SitePlaced" (to)) ) ) stack:True (then (set State at:(var "SitePlaced") (- 2 ("Liberties" Mover Next (var "SitePlaced"))) (then (and (if (= 1 (state at:(var "SitePlaced"))) ("UpdateListStateAroundPlayerBlocker" #1 1 (var "SitePlaced") Mover Next) ) ("AddOrthogonalMarkers") ) ) ) ) ) ) (define "AddAtMoverMarkerExcept" (move Add (piece (id "Ball" Mover)) (to (difference (sites Board) (sites (values Remembered #1)) ) if:(and { (<= 0 (where Level (id "I" Mover) Cell at:(to))) (> 0 (where Level (id "I" Next) Cell at:(to))) (> 4 (count Pieces Next "Ball" in:(sites Around (to) Orthogonal))) //rare special case: empty cell isolated from everything }) (apply (and (remove (to) level:(where Level (id "I" Mover) Cell at:(to))) (set Var "SitePlaced" (to)) ) ) ) stack:True (then (set State at:(var "SitePlaced") (- 2 ("Liberties" Mover Next (var "SitePlaced"))) (then (and (if (= 1 (state at:(var "SitePlaced"))) ("UpdateListStateAroundPlayerBlocker" #1 1 (var "SitePlaced") Mover Next) ) ("AddOrthogonalMarkers") ) ) ) ) ) ) (define "AddAtOpponentMarkerExcept" (move Add (piece (id "Ball" Mover)) (to (difference (sites Board) (sites (values Remembered #1)) ) if:(and (<= 0 (where Level (id "I" Next) Cell at:(to))) (!= 0 ("Liberties" Mover Next (to))) // incase not entered to suicide list yet - eg next to suicide empties on all sides ) (apply (and { (apply (set Var "SitePlaced" (to))) (remove (to) level:(where Level (id "I" Mover) Cell at:(to))) (remove (to) level:(where Level (id "I" Next) Cell at:(to))) }) ) ) stack:True (then (set State at:(var "SitePlaced") (- 2 ("Liberties" Mover Next (var "SitePlaced"))) (then (and ("UpdateListStateAroundPlayerBlocker" #1 (state at:(var "SitePlaced")) (var "SitePlaced") Mover Next ) ("AddOrthogonalMarkers") ) ) ) ) ) ) (define "UpdateStateNext" (if (= 1 (mover)) ("UpdateOpponentsState" "SuicideP2" #1) ("UpdateOpponentsState" "SuicideP1" #1) )) (define "AddExcept" // remembered suicide locations (or { ("AddAtEmptyExcept" #1) ("AddAtMoverMarkerExcept" #1) ("AddAtOpponentMarkerExcept" #1) }) ) //----------------------------------------------- // Main routine (game "Netted" (players 2) (equipment { "BoardUsed" (piece "Ball" P1 maxState:3) (piece "Ball" P2 maxState:3) (piece "I" P1 maxState:6) (piece "I" P2 maxState:6) }) (rules (play (if (= 1 (mover)) ("AddExcept" "SuicideP1") ("AddExcept" "SuicideP2") (then (and { (if (= 1 (mover)) ("RemoveExcessOrthogonals" "SuicideP2") ("RemoveExcessOrthogonals" "SuicideP1") ) ("UpdateStateNext" 1) ("UpdateStateNext" 2) ("AddDiagonalMarkers") ("RemoveExcessDiagonals") }) ) ) ) (end (if (or { (is Triggered "Enmeshed" Mover) (is Triggered "Enmeshed" Next) (no Moves Mover) }) (if (is Triggered "Enmeshed" Next) (result Mover Win) ) (result Mover Loss) ) ) ) ) //------------------------------------------------- // Options // Defines for Options (define "BoardUsed" ) (define "HexLimp" (board (hex Limping (- 1)))) // use:Edge)) (define "Hex2Limp" (board (hex (- 1) (+ 1)))) // use:Edge)) (define "HexCell" (board (hex Hexagon ))) //use:Edge)) (option "Board Grid" args:{ } { //(item "Hex Limping" <"HexLimp"> "Hex N / N-1 Grid") (item "Hex Double Limping" <"Hex2Limp"> "Hex N+1 / N-1 Grid")* (item "Hex Grid" <"HexCell"> "Hex Grid - Standard") } ) (option "Board Size" args:{} { (item "Order 2" <2> "Order 2 board") (item "Order 3" <3> "Order 3 board")* (item "Order 4" <4> "Order 4 board") (item "Order 5" <5> "Order 5 board") (item "Order 6" <6> "Order 6 board") // (item "Order 7" <7> "Order 7 board") // (item "Order 8" <8> "Order 8 board") // (item "Order 9" <9> "Order 9 board") // (item "Order 10" <10> "Order 10 board") // (item "Order 11" <11> "Order 11 board") } ) //--------------------------------------------- (define "ColourBackground" (colour Cream)) (metadata (info { (description "Netted is a race to surround the opponent Nets include Hex diagonals and single empty spaces, and so can cross each other. It is placement only and so can be played as a paper and pencil game. However the computer version adds markers for the empty space connections which must be removed when the spaces are filled. The distinctive features are 1) a novel type of liberty for live groups that involves the count of available edge sites, and 2) the novel definitions for surrounding that include diagonals that cut groups, and connections across single empty spaces that can be broken by the opponent's placement. Moves are forced, suicide is prohibited, stalemate is a loss for the player who is stalemated. Strategy notes: sites next to the board corners are strong ways to gain the corner territory - Board should be at least 5-6 to avoid winnable edge play.") (rules "The goal of Netted is to surround or 'Net' your opponent before being netted yourself. Definitions: A Net is all of a player's stones and the connections between them. Connections include: -- 1. stones on adjacent hexes, -- 2. stones diagonally adjacent (even if the two hexes between then are occupied by the opponent) -- 3. stones on opposite sides of an empty space: The empty space is treated as being part of the net. Regions: A player's Net separates the other player's stones into distinct regions. The stones in each region are a distinct group. No path from one group can reach another group without crossing the net. -- Caution: Adjacent stones, cut between by a diagonal connection, are normally NOT part of the same group. The stones in a group are not necessarily connected, but they live or die together. To live, the region they are in must have MORE edge cells than stones -- equivalently: more empty edge cells than interior stones. Note that any empty edge cells that are part of the opponent's net are not part of the region. To Net: Means to place a stone that reduces the edge liberties of a region, which contains the opponent's stones, to such a degree that the available edge cells (liberties) no longer exceeds the number stones contained in the region. Netting an opponent's stones wins the game. Netting can be done by filling the edges, or by cutting off connections to part or all of the edge -- for example by placing 3 stones in a triangle around an individual piece. Suicide: is a placement of your own stone into a region defined by the opponent's net, bringing your stone count there up to the number of edges cells in the region. Suicide is not allowed. Play: Black starts with an empty board. On your turn place one of your stones on an empty site that has enough liberties to avoid Suicide. The player who Nets his opponent wins. A player who cannot place on their turn, loses.") (id "1652") (version "1.3.12") (classification "experimental") (author "Dale W. Walton") (credit "Dale W. Walton") (date "13-08-2021") } ) (graphics { (player Colour P1 (colour DarkGrey)) (player Colour P2 (colour White)) (piece Colour P1 "Ball" state:0 fillColour:(colour DarkGrey)) (piece Colour P2 "Ball" state:0 fillColour:(colour White)) (piece Colour P1 "Ball" state:1 fillColour:(colour DarkBrown)) (piece Colour P2 "Ball" state:1 fillColour:(colour HumanLight)) (piece Colour P1 "Ball" state:2 fillColour:(colour DarkRed)) (piece Colour P2 "Ball" state:2 fillColour:(colour LightRed)) (piece Scale "Ball" 0.78) //0.98) (piece Colour P1 "I" fillColour:(colour 0 0 0 150)) (piece Colour P2 "I" fillColour:(colour 50 50 50 100)) (piece Rotate "I" state:0 degrees:90) (piece Rotate "I" state:1 degrees:60) (piece Rotate "I" state:2 degrees:30) (piece Rotate "I" state:3 degrees:0) (piece Rotate "I" state:4 degrees:150) (piece Rotate "I" state:5 degrees:120) // (piece Rotate "I" state:6 degrees:90) (piece Scale "I" 1.2) //0.9) (board Colour Phase0 "ColourBackground") (board StyleThickness InnerEdges 0.4) (board StyleThickness OuterEdges 0.6) (board StyleThickness InnerVertices 0.45) (board StyleThickness OuterVertices 0.45) (board Colour InnerVertices (colour Grey)) (board Colour OuterVertices (colour Grey)) (board Colour InnerEdges (colour Black)) (board Colour OuterEdges (colour Black)) (show Edges Diagonal Hidden (colour DarkGrey)) (stackType None) // (show Piece State "I") } ) )