@@ -2,9 +2,10 @@ import { GameBase, IAPGameState, IClickResult, IIndividualState, IValidationResu
22import { APGamesInformation } from "../schemas/gameinfo" ;
33import { APRenderRep , MarkerEdge } from "@abstractplay/renderer/src/schemas/schema" ;
44import { APMoveResult } from "../schemas/moveresults" ;
5- import { Direction , RectGrid , reviver , UserFacingError } from "../common" ;
5+ import { RectGrid , reviver , UserFacingError } from "../common" ;
66import { UndirectedGraph } from "graphology" ;
77import { bidirectional } from "graphology-shortest-path/unweighted" ;
8+ import { type Delta , allRotationsAndReflections } from "../common/plotting" ;
89import i18next from "i18next" ;
910
1011export type playerid = 1 | 2 ;
@@ -35,6 +36,7 @@ export class MinefieldGame extends GameBase {
3536 urls : [
3637 "https://www.marksteeregames.com/Minefield_rules.pdf" ,
3738 "https://boardgamegeek.com/thread/3295906/new-mark-steere-game-minefield" ,
39+ "https://boardgamegeek.com/thread/3299199/cartwheel-possibly-free-of-mutual-zugzwang" ,
3840 ] ,
3941 people : [
4042 {
@@ -52,6 +54,8 @@ export class MinefieldGame extends GameBase {
5254 ] ,
5355 variants : [
5456 { uid : "size-15" , group : "board" } ,
57+ { uid : "#board" , } ,
58+ { uid : "cartwheel" , group : "rules" } ,
5559 ] ,
5660 categories : [ "goal>connect" , "mechanic>place" , "board>shape>rect" , "board>connect>rect" , "components>simple>1per" ] ,
5761 flags : [ "pie" , "automove" , "experimental" ]
@@ -157,86 +161,103 @@ export class MinefieldGame extends GameBase {
157161 return 19 ;
158162 }
159163
160- public canPlace ( cell : string , player : playerid ) : boolean {
161- const [ x , y ] = this . algebraic2coords ( cell ) ;
162- const g = new RectGrid ( this . boardSize , this . boardSize ) ;
164+ public get forbidden ( ) : Delta [ ] [ ] {
165+ const lst : Delta [ ] [ ] = [ ] ;
163166 // hard corners
164- for ( const diag of [ "NE" , "SE" , "SW" , "NW" ] as const ) {
165- let next : string | [ number , number ] = RectGrid . move ( x , y , diag ) ;
166- if ( g . inBounds ( ...next ) ) {
167- next = this . coords2algebraic ( ...next ) ;
168- }
169- // white pieces in the diagram
170- if ( ! Array . isArray ( next ) && this . board . get ( next ) === player ) {
171- const [ ldir , rdir ] = diag . split ( "" ) ;
172- let left : string | [ number , number ] = RectGrid . move ( x , y , ldir as Direction ) ;
173- let lplayer : number | undefined ;
174- if ( g . inBounds ( ...left ) ) {
175- left = this . coords2algebraic ( ...left ) ;
176- lplayer = this . board . get ( left ) ;
177- }
178- let right : string | [ number , number ] = RectGrid . move ( x , y , rdir as Direction ) ;
179- let rplayer : number | undefined ;
180- if ( g . inBounds ( ...right ) ) {
181- right = this . coords2algebraic ( ...right ) ;
182- rplayer = this . board . get ( right ) ;
183- }
184- if ( lplayer === undefined && rplayer !== undefined && rplayer !== player ) {
185- return false ;
186- }
187- if ( rplayer === undefined && lplayer !== undefined && lplayer !== player ) {
188- return false ;
189- }
190- }
191- // black piece in the diagram
192- else if ( ! Array . isArray ( next ) && ! this . board . has ( next ) ) {
193- const [ ldir , rdir ] = diag . split ( "" ) ;
194- let left : undefined | string | [ number , number ] = RectGrid . move ( x , y , ldir as Direction ) ;
195- let lplayer : number | undefined ;
196- if ( g . inBounds ( ...left ) ) {
197- left = this . coords2algebraic ( ...left ) ;
198- lplayer = this . board . get ( left ) ;
199- }
200- let right : string | [ number , number ] = RectGrid . move ( x , y , rdir as Direction ) ;
201- let rplayer : number | undefined ;
202- if ( g . inBounds ( ...right ) ) {
203- right = this . coords2algebraic ( ...right ) ;
204- rplayer = this . board . get ( right ) ;
205- }
206- if ( lplayer !== undefined && lplayer !== player &&
207- rplayer !== undefined && rplayer !== player ) {
208- return false ;
209- }
210- }
211- }
167+ lst . push ( [
168+ { dx : 1 , dy : 0 , payload : null } ,
169+ { dx : 1 , dy : 1 , payload : "f" } ,
170+ { dx : 0 , dy : 1 , payload : "e" } ,
171+ ] ) ;
172+ lst . push ( [
173+ { dx : 0 , dy : - 1 , payload : "e" } ,
174+ { dx : 1 , dy : - 1 , payload : null } ,
175+ { dx : 1 , dy : 0 , payload : "e" } ,
176+ ] ) ;
212177 // switches
213- for ( const orth of [ "N" , "S" , "E" , "W" ] as const ) {
214- const perps = ( orth === "N" || orth === "S" ) ? [ "E" , "W" ] : [ "N" , "S" ] ;
215- for ( const dist of [ 2 , 3 ] ) {
216- let ray = g . ray ( x , y , orth ) . map ( c => this . coords2algebraic ( ...c ) ) ;
217- if ( ray . length >= dist ) {
218- ray = [ cell , ...ray . slice ( 0 , dist ) ] ;
219- for ( const perp of perps ) {
220- let adj : string | [ number , number ] = RectGrid . move ( x , y , perp as Direction ) ;
221- let adjRay : undefined | string [ ] ;
222- if ( g . inBounds ( ...adj ) ) {
223- adjRay = [ this . coords2algebraic ( ...adj ) , ...g . ray ( ...adj , orth ) . map ( c => this . coords2algebraic ( ...c ) ) . slice ( 0 , dist ) ] ;
224- adj = this . coords2algebraic ( ...adj ) ;
225- }
226- if ( adjRay !== undefined ) {
227- // at this point I have two adjacent rays of the appropriate length
228- // populate each with player numbers and then compare (0 is empty)
229- const pRay = ray . map ( c => this . board . get ( c ) ?? 0 ) . join ( "" ) ;
230- const adjPRay = adjRay . map ( c => this . board . get ( c ) ?? 0 ) . join ( "" ) ;
231- // check player ray first
232- if ( / ^ 0 + [ 1 | 2 ] $ / . test ( pRay ) && ! pRay . endsWith ( player . toString ( ) ) ) {
233- // now check adjacent ray
234- if ( / ^ [ 1 | 2 ] 0 + [ 1 | 2 ] $ / . test ( adjPRay ) && ! adjPRay . startsWith ( player . toString ( ) ) && adjPRay . endsWith ( player . toString ( ) ) ) {
235- return false ;
236- }
237- }
238- }
178+ // dist 2
179+ lst . push ( [
180+ { dx : 1 , dy : 0 , payload : "e" } ,
181+ { dx : 1 , dy : 1 , payload : null } ,
182+ { dx : 1 , dy : 2 , payload : "f" } ,
183+ { dx : 0 , dy : 2 , payload : "e" } ,
184+ { dx : 0 , dy : 1 , payload : null } ,
185+ ] ) ;
186+ // dist 3
187+ if ( ! this . variants . includes ( "cartwheel" ) ) {
188+ lst . push ( [
189+ { dx : 1 , dy : 0 , payload : "e" } ,
190+ { dx : 1 , dy : 1 , payload : null } ,
191+ { dx : 1 , dy : 2 , payload : null } ,
192+ { dx : 1 , dy : 3 , payload : "f" } ,
193+ { dx : 0 , dy : 3 , payload : "e" } ,
194+ { dx : 0 , dy : 2 , payload : null } ,
195+ { dx : 0 , dy : 1 , payload : null } ,
196+ ] ) ;
197+ }
198+ if ( this . variants . includes ( "cartwheel" ) ) {
199+ // pinwheel
200+ lst . push ( [
201+ { dx : 1 , dy : 0 , payload : "e" } ,
202+ { dx : 2 , dy : 1 , payload : "f" } ,
203+ { dx : 2 , dy : 2 , payload : "e" } ,
204+ { dx : 1 , dy : 3 , payload : "f" } ,
205+ { dx : 0 , dy : 3 , payload : "e" } ,
206+ { dx : - 1 , dy : 2 , payload : "f" } ,
207+ { dx : - 1 , dy : 1 , payload : "e" } ,
208+ { dx : 0 , dy : 1 , payload : null } ,
209+ { dx : 1 , dy : 1 , payload : null } ,
210+ { dx : 1 , dy : 2 , payload : null } ,
211+ { dx : 0 , dy : 2 , payload : null } ,
212+ ] ) ;
213+ // cartwheel
214+ lst . push ( [
215+ { dx : 1 , dy : 0 , payload : "f" } ,
216+ { dx : 2 , dy : 1 , payload : "e" } ,
217+ { dx : 2 , dy : 2 , payload : "e" } ,
218+ { dx : 1 , dy : 3 , payload : "f" } ,
219+ { dx : 0 , dy : 3 , payload : "f" } ,
220+ { dx : - 1 , dy : 2 , payload : "e" } ,
221+ { dx : - 1 , dy : 1 , payload : "e" } ,
222+ { dx : 0 , dy : 1 , payload : null } ,
223+ { dx : 1 , dy : 1 , payload : null } ,
224+ { dx : 1 , dy : 2 , payload : null } ,
225+ { dx : 0 , dy : 2 , payload : null } ,
226+ ] ) ;
227+ }
228+
229+ return lst ;
230+ }
231+
232+ public canPlace ( cell : string , player : playerid ) : boolean {
233+ const [ x , y ] = this . algebraic2coords ( cell ) ;
234+ for ( const glyph of this . forbidden ) {
235+ for ( const transform of allRotationsAndReflections ( glyph ) ) {
236+ let match = true ;
237+ for ( const delta of transform ) {
238+ const nx = x + delta . dx ;
239+ const ny = y + delta . dy ;
240+ if ( nx < 0 || nx >= this . boardSize || ny < 0 || ny >= this . boardSize ) {
241+ match = false ;
242+ break ;
239243 }
244+ const check = this . coords2algebraic ( nx , ny ) ;
245+ const contents = this . board . get ( check ) ;
246+ if ( delta . payload === "f" && contents !== player ) {
247+ match = false ;
248+ break ;
249+ }
250+ if ( delta . payload === "e" && ( contents === undefined || contents === player ) ) {
251+ match = false ;
252+ break ;
253+ }
254+ if ( delta . payload === null && contents !== undefined ) {
255+ match = false ;
256+ break ;
257+ }
258+ }
259+ if ( match ) {
260+ return false ;
240261 }
241262 }
242263 }
0 commit comments