Strongly solved Ostle: calculating a strong solution helps compose high-quality puzzles for recent games

View article
PeerJ Computer Science

Main article text

 

Introduction

Ultra-weakly solved

If the game-theoretic value of the initial position is determined, then the game is ultra-weakly solved. Note that this definition does not require any actual winning strategy.

Weakly solved

If a strategy to achieve the game-theoretic value of the game for both players, from the initial position, under reasonable computational resources, then the game is weakly solved. For example, checkers was weakly solved in this sense (Schaeffer et al., 2007).

Strongly solved

If the game-theoretic values of all possible legal positions are determined for both players, then the game is strongly solved. Note that a winning strategy can easily be obtained once a strong solution is given (i.e., the theoretical values of the positions after each legal move from the current position can be seen). Retrograde analysis (Thompson, 1986) is a standard method for strongly solving a pure strategy board game.

Methods

The rules of Ostle

  1. Black moves first, after which the players alternate.

  2. On each player’s turn, that player must choose either one of his/her owned pieces or the hole to be moved and move it in one square up, down, left, or right.

  3. A pass is not allowed; both players must move.

  4. If a piece reaches the hole or outside the board, it is removed from the game. Moving a player’s piece to the hole or outside the board is allowed.

  5. Pieces can be moved to a square occupied by another piece. In this case, the original piece is pushed out and moved one square in the same direction. This process is recursive until a piece reaches an empty square, the hole, or the outside of the board (cf. Figs. 2A and 2C).

  6. A player wins when the opponent has only three pieces left. For example, Black wins in Fig. 2D.

  7. The holes can only be moved to empty squares. For example, in Fig. 2B, White moves the hole from a3 to a2 but cannot move it to b3 or a4 (because they are not empty) or to the left (because the left is outside the board).

  8. Any position must not be the same as two plies before it. Any move that causes such a situation is restricted. For example, in Fig. 2C, Black cannot move the hole to a3 because the position after the ply would be exactly the same as the previous position (Fig. 2B).

Notation and preliminary

  • A finite set P of positions, such that every p ∈ P corresponds to a unique arrangement of pieces on the board of an unfinished game, including the hole, as well as which player’s turn it is. Note that every p ∈ P includes either four or five white and four or five black pieces.

  • A finite set Mp of moves associated to every p ∈ P. Moves are represented by an alphanumeric coordinates for the square and an uppercase letter for the direction (U, D, L, or R for up, down, left, or right, respectively). For example, the chosen moves in Fig. 2 are represented as e3L (Fig. 2A), a3U (Fig. 2B), and b3U (Fig. 2C).

  • A finite set S of states, such that each s ∈ S consists of a unique tuple (ps,mrs), where ps ∈ P and mrs(Mps{ϕ}). The first element of the tuple identifies a position, and the second identifies which move is restricted (if applicable).

  • A function ftransition that takes a position p ∈ P and its move m ∈ Mp as arguments, such that ftransition(pm) returns a state s ∈ S such that p transitions to s by m. A ply is represented by ftransition.

  • A function fsquare associated with every p ∈ P takes a move m ∈ Mp as arguments, such that fsquare(m) returns m’s source square. For example, in Fig. 2A, if m is e3L, then fsquaree3L =e3.

  • A function fdirection associated with every p ∈ P that takes a move m ∈ Mp as argument, such that fdirection(m) returns the direction of m. For example, in Fig. 2A, if m is e3L, then fdirectione3L =L.

  • A boolean-valued function fif_PRS takes a state s ∈ S as an argument, such that fif_PRS(s) returns True if and only if a position p ∈ P and an associated move m ∈ Mp exist such that p transitions to s by m. A possibly reachable state is a state s for which fif_PRS(s) is True. Note that it does not consider whether (p′, m) ∈ S or not. In other words, if s is not a possibly reachable state, s is guaranteed to be unreachable from any state; however, if s is a possibly reachable state, it does not follow that s is guaranteed to be reachable.

  • A boolean-valued function g that takes a state s ∈ S as an argument, such that g(s) returns True if and only if a legal move mMps{mrs} exists such that m′ wins the game for the player to move. A checkmate state is a state s for which g(s) is True. For example, Fig. 2C is a checkmate state because Black wins with b3U. Additionally, a position p ∈ P is called a checkmate position if and only if all corresponding states of {s ∈ S:ps = pfif_PRS(s) = True} are checkmate states.

  • A boolean-valued function h that takes a state s ∈ S as an argument, such that h(s) = fif_PRS(s)∧(¬g(s)). A state s ∈ S is called a non-trivial state if and only if h(s) is True.

  • Assume that ijk are integers and i ≤ j < k. Here, a bracket notation [ij] indicates the integer interval between i and j, including both. Another bracket notation [ik) also indicates an integer interval, but k is excluded. In other words, [ik) = {ii + 1, …, k − 1}.

Theorem 1

Any move which removes a piece cannot be restricted.

Proof 1

In Ostle, the number of pieces on the board decreases monotonically because no ply increases pieces. For this reason, for an arbitrary move m which removes a piece, the position after m is different from the position two plies before m in terms of the number of pieces. Therefore, m is never restricted by rule 8.

Theorem 2

Any move which wins the game cannot be restricted.

Proof 2

Assume that a move m ∈ Mp from a position p ∈ P wins the game. This means that there are just four opponent’s pieces in p, and m removes an opponent’s piece. By the Theorem 1, we can conclude that m is never restricted.

Theorem 3

If a state s ∈ S is a checkmate state, then the corresponding position ps is always a checkmate position.

Proof 3

The proof is by contradiction. Assume that there exists a state s′ ∈ S such that s′ is a checkmate state, but corresponding position ps is not a checkmate position. Then there must exist a move m ∈ Mps such that m wins for the player to move.

From the definition of a checkmate position, there must exist a state s ∈ S such that ps = pfif_PRS(s) = True. Note that m ∈ Mps because a set of moves is associated only with a position, not a state. In order to satisfy the condition fif_PRS(s) = True, m must be restricted in the state s. This is in contradiction to Theorem 2.

Move generation preliminaries

 
____________________________ 
Algorithm 1 G(p): Generate all moves in a predetermined order.__________________________________________ 
Require: p: A position. Note that the pieces must be labeled not as Black or White, but as Self 
     (the player to move) or Opponent. 
  1:  a ← an empty list 
  2:  b ←{‘a’,‘b’,‘c’,‘d’,‘e’}×{‘1’,‘2’,‘3’,‘4’,‘5’} 
 3:  b ← sort(list(b))                                ⊳ A list of all squares’ names in lexicographical order. 
  4:  for s ∈ b do 
 5:       if The hole exists on square s then 
 6:            if The hole can be legally moved up then 
 7:                  a.append(s+‘U’) 
  8:            end if 
 9:            if The hole can be legally moved down then 
10:                  a.append(s+‘D’) 
11:            end if 
12:            if The hole can be legally moved left then 
13:                  a.append(s+‘L’) 
14:            end if 
15:            if The hole can be legally moved right then 
16:                  a.append(s+‘R’) 
17:            end if 
18:       else if A Self’s piece exists on square s then 
19:            for d ∈{‘U’,‘D’,‘L’,‘R’} do 
20:                  a.append(s + d) 
21:            end for 
22:       end if 
23:  end for 
24:  return a_______________________________________________________________________________________________    
Theorem 4

For all p ∈ P and m1m2 ∈ Mp, if fsquare(m1) ≠ fsquare(m2), then ftransition(pm1) ≠ ftransition(pm2).

Proof 4

After an arbitrary move, the square from which the piece was moved becomes empty. In contrast, the other squares never become empty if they were originally not empty. Therefore, ftransition(pm1) ≠ ftransition(pm2) in terms of whether the square from which the piece was moved is empty.

Theorem 5

For all p ∈ P and m1m2 ∈ Mp ( m1 ≠ m2), if neither m1 nor m2 remove any pieces, then ftransition(pm1) ≠ ftransition(pm2).

Proof 5

According to theorem 4, if fsquare(m1) ≠ fsquare(m2), then ftransition(pm1) ≠ ftransition(pm2). In the following, we will consider the case where fsquare(m1) = fsquare(m2). Let us denote Q = fsquare(m1) = fsquare(m2). Because m1 ≠ m2, m1 and m2 differ in the directions. Because neither m1 nor m2 remove any pieces, for each i ∈ {1, 2}, there is one square that is empty at p but filled at ftransition(pmi); let us denote the square by Qi. Note that Qi is in the direction of mi from Q. Then Q1 ≠ Q2 if m1 ≠ m2, and m1 and m2 are in different directions. Consequently, if m1 ≠ m2 and fsquare(m1) = fsquare(m2), then ftransition(pm1) ≠ ftransition(pm2) in terms of the square that is empty at p but filled at ftransition(pmi).

Theorem 6

For all p ∈ P, the number of restricted moves in G(p) is at most one.

Proof 6

Taking the contraposition of theorem 5, we can find that for all m1m2 ∈ G(p) (m1 ≠ m2)), if ftransition(pm1) = ftransition(pm2), then m1 and m2 are moves removing a piece. Using theorem 1, we can find that such m1 and m2 are never restricted. Taking the contraposition of this, we can conclude that for all mm′ ∈ G(p) (m ≠ m′), if m is restricted, then ftransition(pm) ≠ ftransition(pm′), hence ftransition(pm′) is not restricted. Therefore, for all p ∈ P and m ∈ G(p), if m is restricted, then all the other moves in G(p) never restricted.

Positional symmetry

 
_____________________________________________________________________________________________________________ 
Algorithm 2 E(p): Enumerate all symmetric positions._______________________________________________________ 
Require: p: A position (e.g., a 5 × 5 matrix). 
Require: flip_lr(p): A function that horizontally flips the argument position. 
Require: flip_ud(p): A function that vertically flips the argument position. 
Require: transpose(p): A function that transposes the argument position. 
  1:  a ← an empty set 
  2:  for i ← [0,7] do 
 3:       x ← p 
 4:       if (i&1) ⁄= 0 then                    ⊳ The “&” symbols refer to the bitwise-and operation. 
  5:            x ← flip_lr(x) 
  6:       end if 
 7:       if (i&2) ⁄= 0 then 
 8:            x ← flip_ud(x) 
  9:       end if 
10:       if (i&4) ⁄= 0 then 
11:            x ← transpose(x) 
12:       end if 
13:       a.add(x) 
14:  end for 
15:  return a_______________________________________________________________________________________________    
 
_____________________________________________________________________________________________________________ 
Algorithm 3 U(p): Get a unique representative position among the symmetric positions.__________ 
Require: p: A position (e.g., a 5 × 5 matrix). 
Require: ptoi(p): An injective function that maps positions to integers. 
  1:  a ← p 
 2:  for x ∈ E(p) do 
 3:       if ptoi(x) < ptoi(a) then 
 4:            a ← x 
 5:       end if 
 6:  end for 
 7:  return a_______________________________________________________________________________________________    

Enumerating positions

  • Checkmate positions were included in the enumeration. In contrast, positions after the game were over (i.e., positions where a loser had only three pieces) were not included.

  • Both players had no obligation to win in any checkmate position. In other words, positions that are unreachable from the initial position without overlooking a winning move were included in the enumeration.

  • Only positions in which it was Black’s turn to move were enumerated. In other words, in the following, “Black” means “the player to move”, and “White” means “their opponent”, except where specifically noted otherwise. This is sufficient because there is a sequence of moves whereby the same position is reached, but the player to move is changed (an example is shown in Fig. 3).

  • Symmetric positions were considered identical. This is sufficient because there is a sequence of moves to rotate the initial position ninety degrees (an example is shown in Fig. 4). The other symmetric positions can be reached by repeating the sequence two or three times.

Observation 1

Observation 2

Algorithms

 
_____________________________________________________________________________________________________________ 
Algorithm 4 Enumerate and sort all positions.__________________________________________________________________ 
  1:  v ← an empty list 
  2:  for (b,w,h) ∈{4,5}×{4,5}×{0,1,2,6,7,12} do              ⊳ This for-loop is parallelizable. 
  3:       w ← an empty list 
  4:       p ← an empty position (e.g., a dictionary) 
  5:       w ← D(w,p,0,b,w,h)                                               ⊳ The function D is Algorithm 5. 
  6:       for i ∈ [0,len(w)) do 
 7:            w[i] ← U(w[i])                                                    ⊳ The function U is Algorithm 3. 
  8:       end for 
 9:       w.uniquify()  ⊳ e.g., in C++, std::sort and std::unique are available; in Python, list(set(w)) 
     is. 
10:       v.concatenate(w)               ⊳ If executed in parallel, this line must be in a critical section. 
11:  end for 
12:  return sort(v)________________________________________________________________________________________________________    
 
_________________________________________________________________________________________________________________________________ 
Algorithm 5 D(v,p,n,b,w,h): An auxiliary function of depth-first search to enumerate positions. 
Require: v: A list of positions. 
Require: p: An in-process position (e.g., a dictionary). 
Require: n: An integer representing a considering square ( n ∈ [0,25] ). 
Require: b: An integer representing a number of remaining Black’s pieces ( b ∈ [0,5] ). 
Require: w: An integer representing a number of remaining White’s pieces ( w ∈ [0,5] ). 
Require: h: An integer representing the square where the hole exists ( h ∈{0,1,2,6,7,12} ). 
  1:  if n = 25 then 
 2:       v.append(p) 
  3:       return v 
 4:  end if 
 5:  if n = h then 
 6:       p[n] ← ”hole” 
  7:       return D(v,p,n + 1,b,w,h) 
  8:  end if 
 9:  if b > 0 then 
10:       q ← p 
11:       q[n] ← ”black” 
12:       v.concatenate(D(v,q,n + 1,b − 1,w,h)) 
13:  end if 
14:  if w > 0 then 
15:       q ← p 
16:       q[n] ← ”white” 
17:       v.concatenate(D(v,q,n + 1,b,w − 1,h)) 
18:  end if 
19:  v.concatenate(D(v,p,n + 1,b,w,h)) 
20:  return v_______________________________________________________________________________________________ 

Enumerating non-trivial states

 
_____________________________________________________________________________________________________________ 
Algorithm 6 B(P,c =False): Make a bitvector that represents whether each state is non-trivial._ 
Require: P: A sorted list of all positions (return value of Algorithm 4). 
Require: c: A boolean flag to control whether checkmate positions are counted. 
  1:  v ← a zero-filled bitvector whose length is |P|× 25. 
  2:  for p ∈ P do ⊳ This for-loop is parallelizable; but if parallelized, the operation of setting a bit 
     of v must be atomic or executed in a critical section. 
  3:       M ← G(p)                                                      ⊳ Generate all moves of the position p. 
  4:       for m ∈ M do 
 5:            s ← ftransition(p,m) 
  6:            if c =True or g(s) =False then 
 7:                  i ← the integer such that P[i] = U(E(ps))              ⊳ e.g., perform a binary search. 
  8:                  M′ ← G(P[i])         ⊳ Generate all moves of the position P[i]. Note that |M′|≤ 24. 
  9:                  for j ∈ [0,|M′|) do 
10:                       s′ ← ftransition(P[i],M′[j]) 
11:                       if U(E(ps′)) = p then 
12:                            v[i × 25 + j + 1] ← 1                   ⊳ Set the (i × 25 + j + 1)-th bit of v to 1. 
13:                            goto END: 
14:                       end if 
15:                  end for 
16:                  v[i × 25] ← 1                                                 ⊳ Set the (i × 25)-th bit of v to 1. 
17:                  END: 
18:            end if 
19:       end for 
20:  end for 
21:  return v_______________________________________________________________________________________________    

Bitvector and succinct indexable dictionary

Retrograde analysis

 
_____________________________________________________________________________________________________________ 
Algorithm 7 Retrograde analysis of Ostle________________________________________________________________________ 
Require: A(i,v,x,y,lwin,llose,ldraw): Auxiliary function, which is described below as Algorithm 
     8. 
  1:  P ← A sorted list of all positions (i.e., return value of Algorithm 4). 
  2:  v ← B(P)                                                                                ⊳ B(P) is Algorithm 6. 
  3:  Convert v into succinct bitvector which supports rank query in constant time. 
  4:  x ← A zero-filled vector of which length is popcount(v) = 11,148,725,918. 
  5:  while True do 
 6:       y ← x 
 7:       for i ∈ [0,|P|) do                                               ⊳ This for-loop is parallelizable. 
  8:            lwin,llose,ldraw ← empty lists. 
  9:            M ← G(P[i])                                            ⊳ generate all moves of the position P[i]. 
10:            for j ∈ [0,|M|) do 
11:                  s ← ftransition(P[i],M[j]) 
12:                  k ← the integer such that P[k] = U(E(ps))                   ⊳ perform a binary search. 
13:                  if g(s) then                                                      ⊳ s is a checkmate state. 
14:                       llose.append((−1,j)) 
15:                  else 
16:                       if r ∈ [0,24) exists such that G(P[k])[r] is a restricted move then 
17:                            k ← k + r + 1 
18:                       end if 
19:                       if x[v.rank(k)] is a negative number then 
20:                            lwin.append((−x[v.rank(k)] + 1,j)) 
21:                       else if x[v.rank(k)] is a positive number then 
22:                            llose.append((−x[v.rank(k)] − 1,j)) 
23:                       else 
24:                            ldraw.append(j) 
25:                       end if 
26:                  end if 
27:            end for 
28:            Sort the elements of lwin and ones of llose in ascending order. 
29:            y ← A(i,v,x,y,lwin,llose,ldraw)  ⊳ If parallelized, this line must be in a critical section. 
30:       end for 
31:       if y = x then                                     ⊳ i.e., no state was updated in this iteration. 
32:            break 
33:       end if 
34:       x ← y 
35:  end while 
36:  return x_______________________________________________________________________________________________    
 
_____________________________________________________________________________________________________________ 
Algorithm 8 A(i,v,x,y,lwin,llose,ldraw): Auxiliary function for retrograde analysis of Ostle______ 
Require: i,v,x,y,lwin,llose,ldraw: variables appear in Algorithm 7. 
Require: Elements of lwin and ones of llose is already sorted in ascending order. 
  1:  λ(n) = if n ≤ 0, return inf; otherwise, return n.                ⊳ A function used in the following. 
  2:  λternary(a,b,c) = if a is True, return b; otherwise, return c. ⊳ A function used in the following. 
  3:  for j ∈ [0,25) do 
 4:       if v[i× 25 + j] = 1 then ⊳ Only if the best move is restricted, choose the second-best move. 
  5:            k ← v.rank(i × 25 + j) 
  6:            if lwin has two or more elements then 
 7:                  y[k] ← λternary(j = 1 + lwin[0][1],min(λ(x[k]),lwin[1][0]),min(λ(x[k]),lwin[0][0])) 
  8:            else if lwin has only one element, and ldraw has one or more elements then 
 9:                  y[k] ← λternary(j = 1 + lwin[0][1],max(x[k],0),min(λ(x[k]),lwin[0][0])) 
10:            else if lwin has only one element, and ldraw has no element then 
11:                  y[k] ← λternary(j = 1 + lwin[0][1],max(x[k],llose[0][0]),min(λ(x[k]),lwin[0][0])) 
12:            else if lwin has no element, and ldraw has two or more elements then 
13:                  y[k] ← max(x[k],0) 
14:            else if lwin has no element, and ldraw has only one element then 
15:                  y[k] ← λternary(j = 1 + ldraw[0],max(x[k],llose[0][0]),max(x[k],0)) 
16:            else if Neither lwin nor ldraw has any element then 
17:                  y[k] ← λternary(j = 1 + llose[0][1],max(x[k],llose[1][0]),max(x[k],llose[0][0]) 
18:            else 
19:                  assert False 
20:            end if 
21:       end if 
22:  end for 
23:  return y_______________________________________________________________________________________________ 

Breadth-first search to prove the reachability

 
_____________________________________________________________________________________________________________ 
Algorithm 9 Breadth-first search of Ostle________________________________________________________________________ 
  1:  P ← A sorted list of all positions (i.e., return value of Algorithm 4). 
  2:  v ← B(P, True)                                                                         ⊳ B(P) is Algorithm 6. 
  3:  Convert v into succinct bitvector which supports rank query in constant time. 
  4:  x ← A inf-filled vector of which length is popcount(v). 
  5:  I ← The index such that P[I] is the initial position shown in Fig. 1A. 
  6:  x[v.rank(I × 25)] ← 0 
  7:  for d ∈ [0,inf) do 
 8:       y ← x 
 9:       for i ∈ [0,|P|) do                                               ⊳ This for-loop is parallelizable. 
10:            M ← G(P[i])                                            ⊳ generate all moves of the position P[i]. 
11:            for j ∈ [0,25) do 
12:                  if x[v.rank(i × 25 + j)] = d then 
13:                       M′ ← G(P[i])                                  ⊳ generate all moves of the position P[i]. 
14:                       for k ∈ [0,|M′|) ∖{j − 1} do 
15:                            s ← ftransition(P[i],M[k]) 
16:                            l ← the integer such that P[l] = U(E(ps))     ⊳ e.g., perform a binary search. 
17:                            a ← v.rank(l × 25 + k) 
18:                            y[a] ← min(d + 1,x[a]) ⊳ If parallelized, this line must be in a critical section. 
19:                       end for 
20:                  end if 
21:            end for 
22:       end for 
23:       if y = x then                                     ⊳ i.e., no state was updated in this iteration. 
24:            break 
25:       end if 
26:       x ← y 
27:  end for 
28:  return x_______________________________________________________________________________________________    

Computational resource

Results

Enumerating positions

Obtaining non-trivial states

Retrograde analysis

States taking 147 plies to win

Breadth-first search

Discovering interesting states and composing a tactical problem from one of them

Solution to the puzzle in Fig. 1B

Discussion and future works

Conclusions

Additional Information and Declarations

Competing Interests

The author declare that there are no competing interests.

Author Contributions

Hiroki Takizawa conceived and designed the experiments, performed the experiments, analyzed the data, performed the computation work, prepared figures and/or tables, authored or reviewed drafts of the article, and approved the final draft.

Data Deposition

The following information was supplied regarding data availability:

The source code is available at GitHub and Zenodo: https://github.com/eukaryo/ostle_solver

Hiroki Takizawa. (2023). eukaryo/ostle_solver: v1.0.2.0 (v1.0.2.0). Zenodo. https://doi.org/10.5281/zenodo.8242219

The outputs of analyses are available at figshare:

Takizawa, Hiroki (2022). ostle_solver_output.tar.bz2. figshare. Dataset. https://doi.org/10.6084/m9.figshare.19668789.v1.

Funding

The author received no funding for this work.

894 Visitors 855 Views 31 Downloads

Your institution may have Open Access funds available for qualifying authors. See if you qualify

Publish for free

Comment on Articles or Preprints and we'll waive your author fee
Learn more

Five new journals in Chemistry

Free to publish • Peer-reviewed • From PeerJ
Find out more