Skip to main content
deleted 18 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65

Ruby 2.7, 695...625618 618610 bytes

i=*0..7
t,*b={}
"?>=<765/.'".bytes{b[_1],b[~_1]=A=?X,E=?O}
loop{puts'
 '+[*?a..?h]*' ',a='+-'*8+?+,i.map{|j|"|#{b[8*~j,8].map{_1||?.}*?|}| #{8-j}
"+a}
eval"M,*m=->u,w=p,*s{Q=(0..8).map{c,r=u;x,y=ur=u;z=c+8*r
#{f='$f='(v=x+=_1v=c+=_1/3-1,y+=_1%3r+=_1%3-1)-i==[]&&'}(k=b[x+8*y]k=#{$g='b[c+8*r]'}
w ?b[z=c+8*r]==A&&b[z]==A&&(t[A]!=z&&(#{g=f+'#$f!b[x+8*y]'}&&#$g&&(k==A||k&&N[u]&N[v]==[])&&s<<u+v
xc,y=u;s<<u+vr=u;s<<u+v while#{g}while#$f!#$g);k):k!=E&&s<<v)};s}
N,*q=->o,n=M[o]{n==n|=n.flat_map{M[_1]}||redo;n}
i.product(i){#{h='M[_1,1];A,E=E,A;([p,A]&Q)[0]&&'}(p A;Z);m+=e=#{h}q+=e}
A,E=E,A
($><<A+?>;c,r,_,x,y=gets.bytes.map{~-_1%8})until[[c,r,x,y]]-(m[0]?q[0]?q:m:(p E;Z))==[]
b[c+8*r]#$g,b[t[A]=x+8*y]=p,A"}

Try it online!Try it online! (628620 bytes because the older Ruby on TIO doesn't support numbered block parameters.) A simple example game is included that illustrates most of Quagmire's rules. Just for clarity, the input is commented and invalid moves are indented.

Now the main loop begins, running until one player wins. Following the 3 lines that display the board, the remainder of the program is enclosed in an eval string, which allows a few chunks of code (stored in the strings f$f, g$g, and h) to be recycled. The first 5 lines of this string contain the real meat of the program: the lambdas M and N.

Let's start with N. N finds the set of all squares that are connected to the square at column c and row r (0-indexed), stored as the co-ordinate pairordinates o (co-ordinates are pairs of 0-indexed column and row numbers). Here, connected means that there is a path between all the squares that is not blocked by enemy pieces. Source and destination squares must be unconnected as a prerequisite for jumping an enemy piece. N fans out from the current square, adding its non-enemy neighbours (n=M[o]) and their neighbours (n|=n.flat_map{M[_1]) to the set n by calling M (see below). We redo this process, adding more neighbours of neighbours, until n no longer changes (i.e. until all connected squares have been found).

M probes the board around the square at column c and row r, stored as the co-ordinate pairordinates u. Its exact behaviour is determined by the switch w, which takes a value of either nil (a.k.a. p) or 1:

  • When w is nil, M returns an array s of the co-ordinates of all neighbours (8-neighbourhood) of the current square at u that do not contain enemy pieces (k!=E; k=b[x+8*y] is the value of the neighbouring square). Lambda N uses this information to find the set of all squares that are connected to the current square, as described above. The co-ordinates of each neighbour (column xc and row yr, stored as the co-ordinate pair v) are obtained by independently advancing c and r by -1, 0, or 1 ((0..8).map...(v=x+=_1v=c+=_1/3-1,y+=_1%3r+=_1%3-1)), ensuring that xc and yr both lie within the board ((v=...)-i==[]). TheThis code for finding neighboursadjacent squares is required again later, so to save bytes it is stored as a string in f$f and interpolated into the eval string. Similarly, the code that returns the value of the square at v is saved in $g ($g='b[c+8*r]').

  • When w equals 1 and the current square at u contains one of the active player's pieces (b[z=c+8*r]==Ab[z]==A), M does two things:

    1. It returns an array s containing all possible moves for the active player originating from the currentthis square.

    2. It saves the values of all neighbouring squares in Q, which is used later to test for quagmire.

    Moves are only allowed if the piece was not moved on the previous turn (t[A]!=z). For jumps, the destination square is found by advancing xc and yr in the same directions once againa second time (by interpolating f$f) and testing whether this square is empty (!b[x+8*y]#$g). This code snippet is required later for ordinary moves and so is saved in g. For a jump to be possible, there must either be an active piece to jump over (k==A) or, if an enemy piece is being jumped, the source and destination squares must be unconnected (k&&N[u]&N[v]==[]). For ordinary moves, xc and yr are reset (xc,y=ur=u) and then both variables are advanced in the same directions as before, one square at a time, until no further move along that path is possible (while#{g}while#$f!#$g). Moves are saved (s<<u+v) in the format [c,r,x,y], that is, the column and row numbers of the source square followed by the column and row numbers of the destination.

  1. Loop over all squares of the board by forming pairs of column and row indicesnumbers (i.product(i)).

  2. For the current square, complete the following steps, all saved in h:

    • Call M in move-finding mode (M[_1,1]). If the square contains an enemy piece, update Q with the values of the neighbouring squares. (Any possible moves for that piece are also returned, but we immediately discard them: it's not the opponent's turn to move, after all!)

    • Interchange the active and enemy pieces (A,E=E,A). The reason for interchanging players is that quagmire checks are performed twice per square, once (here in step 2) to find whether the opponent has left a piece in quagmire and again (in step 4) to find whether the active player has been quagmired by the opponent's previous move.

    • Test whether the current square contains a quagmired enemy piece (([p,A]&sA]&Q)[0]). ([p,A]&sA]&Q)[0] is truthy only if there are no empty neighbours and at least one neighbour is an active piece.

  3. If the current square contains a quagmired enemy piece then the game is won. Print the active player's symbol (p A) and terminate by throwing a tantrum (Z is not defined).

  4. Repeat step 2, reading 'active' for 'enemy' and vice versa, but this time add all possible moves (neglecting quagmire for the moment) for the active player to m and keep a copy in e (m+=e=#{h}). If the current square contains a quagmired active piece then add e to q as well (q+=e).

  5. Outside the loop, interchange the active/enemy pieces to advance the turn (A,E=E,A).

  6. Finally it's time for some input!

    • We now have two lists of moves for the active player: m contains all possible moves (neglecting quagmire) whereas q (a subset of m) contains possible moves by any quagmired pieces. (m[0]?q[0]?q:m:(p E;Z)) decides which move list should be used. If m is empty (i.e. the active player has no possible moves) then the game is lost; print the opponent's symbol and terminate (m[0]?...:(p E;Z)). If q is nonempty it takes precedence over m (q[0]?q:m).

    • Until a valid move is entered correctly (until[[c,r,x,y]]-(...)==[]), display the prompt ($><<A+?>), and convert the input to codepoints (gets.bytes), then convert these to 0-indexed column/row numbers (gets.bytes.map{~-_1%8}).

  7. Update the source and destination squares, saving the destination index in t (b[c+8*r]#$g,b[t[A]=x+8*y]=p,A).

Ruby 2.7, 695...625 618 bytes

i=*0..7
t,*b={}
"?>=<765/.'".bytes{b[_1],b[~_1]=A=?X,E=?O}
loop{puts'
 '+[*?a..?h]*' ',a='+-'*8+?+,i.map{|j|"|#{b[8*~j,8].map{_1||?.}*?|}| #{8-j}
"+a}
eval"M,*m=->u,w=p,*s{Q=(0..8).map{c,r=u;x,y=u
#{f='(v=x+=_1/3-1,y+=_1%3-1)-i==[]&&'}(k=b[x+8*y]
w ?b[z=c+8*r]==A&&(t[A]!=z&&(#{g=f+'!b[x+8*y]'}&&(k==A||k&&N[u]&N[v]==[])&&s<<u+v
x,y=u;s<<u+v while#{g});k):k!=E&&s<<v)};s}
N,*q=->o,n=M[o]{n==n|=n.flat_map{M[_1]}||redo;n}
i.product(i){#{h='M[_1,1];A,E=E,A;([p,A]&Q)[0]&&'}(p A;Z);m+=e=#{h}q+=e}
A,E=E,A
($><<A+?>;c,r,_,x,y=gets.bytes.map{~-_1%8})until[[c,r,x,y]]-(m[0]?q[0]?q:m:(p E;Z))==[]
b[c+8*r],b[t[A]=x+8*y]=p,A"}

Try it online! (628 bytes because the older Ruby on TIO doesn't support numbered block parameters.) A simple example game is included that illustrates most of Quagmire's rules. Just for clarity, the input is commented and invalid moves are indented.

Now the main loop begins, running until one player wins. Following the 3 lines that display the board, the remainder of the program is enclosed in an eval string, which allows a few chunks of code (stored in the strings f, g, and h) to be recycled. The first 5 lines of this string contain the real meat of the program: the lambdas M and N.

Let's start with N. N finds the set of all squares that are connected to the square at column c and row r (0-indexed), stored as the co-ordinate pair o. Here, connected means that there is a path between all the squares that is not blocked by enemy pieces. Source and destination squares must be unconnected as a prerequisite for jumping an enemy piece. N fans out from the current square, adding its non-enemy neighbours (n=M[o]) and their neighbours (n|=n.flat_map{M[_1]) to the set n by calling M (see below). We redo this process, adding more neighbours of neighbours, until n no longer changes (i.e. until all connected squares have been found).

M probes the board around the square at column c and row r, stored as the co-ordinate pair u. Its exact behaviour is determined by the switch w, which takes a value of either nil (a.k.a. p) or 1:

  • When w is nil, M returns an array s of the co-ordinates of all neighbours (8-neighbourhood) of the current square that do not contain enemy pieces (k!=E; k=b[x+8*y] is the value of the neighbouring square). Lambda N uses this information to find the set of all squares that are connected to the current square, as described above. The co-ordinates of each neighbour (column x and row y, stored as the co-ordinate pair v) are obtained by independently advancing c and r by -1, 0, or 1 ((0..8).map...(v=x+=_1/3-1,y+=_1%3-1)), ensuring that x and y both lie within the board ((v=...)-i==[]). The code for finding neighbours is required again later, so to save bytes it is stored as a string in f and interpolated into the eval string.

  • When w equals 1 and the current square contains one of the active player's pieces (b[z=c+8*r]==A), M does two things:

    1. It returns an array s containing all possible moves for the active player originating from the current square.

    2. It saves the values of all neighbouring squares in Q, which is used later to test for quagmire.

    Moves are only allowed if the piece was not moved on the previous turn (t[A]!=z). For jumps, the destination square is found by advancing x and y in the same directions once again (by interpolating f) and testing whether this square is empty (!b[x+8*y]). This code snippet is required later for ordinary moves and so is saved in g. For a jump to be possible, there must either be an active piece to jump over (k==A) or, if an enemy piece is being jumped, the source and destination squares must be unconnected (k&&N[u]&N[v]==[]). For ordinary moves, x and y are reset (x,y=u) and then both variables are advanced in the same directions as before, one square at a time, until no further move along that path is possible (while#{g}). Moves are saved (s<<u+v) in the format [c,r,x,y].

  1. Loop over all squares of the board by forming pairs of column and row indices (i.product(i)).

  2. For the current square, complete the following steps, all saved in h:

    • Call M in move-finding mode (M[_1,1]). If the square contains an enemy piece, update Q with the values of the neighbouring squares. (Any possible moves for that piece are also returned, but we immediately discard them: it's not the opponent's turn to move, after all!)

    • Interchange the active and enemy pieces (A,E=E,A). The reason for interchanging players is that quagmire checks are performed twice per square, once (here in step 2) to find whether the opponent has left a piece in quagmire and again (in step 4) to find whether the active player has been quagmired by the opponent's previous move.

    • Test whether the current square contains a quagmired enemy piece (([p,A]&s)[0]). ([p,A]&s)[0] is truthy only if there are no empty neighbours and at least one neighbour is an active piece.

  3. If the current square contains a quagmired enemy piece then the game is won. Print the active player's symbol (p A) and terminate by throwing a tantrum (Z is not defined).

  4. Repeat step 2, reading 'active' for 'enemy' and vice versa, but this time add all possible moves (neglecting quagmire for the moment) for the active player to m and keep a copy in e (m+=e=#{h}). If the current square contains a quagmired active piece then add e to q as well (q+=e).

  5. Outside the loop, interchange the active/enemy pieces to advance the turn (A,E=E,A).

  6. Finally it's time for some input!

    • We now have two lists of moves for the active player: m contains all possible moves (neglecting quagmire) whereas q (a subset of m) contains possible moves by any quagmired pieces. (m[0]?q[0]?q:m:(p E;Z)) decides which move list should be used. If m is empty (i.e. the active player has no possible moves) then the game is lost; print the opponent's symbol and terminate (m[0]?...:(p E;Z)). If q is nonempty it takes precedence over m (q[0]?q:m).

    • Until a valid move is entered correctly (until[[c,r,x,y]]-(...)==[]), display the prompt ($><<A+?>), convert the input to codepoints (gets.bytes), then convert these to 0-indexed column/row numbers (map{~-_1%8}).

  7. Update the source and destination squares, saving the destination index in t (b[c+8*r],b[t[A]=x+8*y]=p,A).

Ruby 2.7, 695...618 610 bytes

i=*0..7
t,*b={}
"?>=<765/.'".bytes{b[_1],b[~_1]=A=?X,E=?O}
loop{puts'
 '+[*?a..?h]*' ',a='+-'*8+?+,i.map{|j|"|#{b[8*~j,8].map{_1||?.}*?|}| #{8-j}
"+a}
eval"M,*m=->u,w=p,*s{Q=(0..8).map{c,r=u;z=c+8*r
#{$f='(v=c+=_1/3-1,r+=_1%3-1)-i==[]&&'}(k=#{$g='b[c+8*r]'}
w ?b[z]==A&&(t[A]!=z&&(#$f!#$g&&(k==A||k&&N[u]&N[v]==[])&&s<<u+v
c,r=u;s<<u+v while#$f!#$g);k):k!=E&&s<<v)};s}
N,*q=->o,n=M[o]{n==n|=n.flat_map{M[_1]}||redo;n}
i.product(i){#{h='M[_1,1];A,E=E,A;([p,A]&Q)[0]&&'}(p A;Z);m+=e=#{h}q+=e}
A,E=E,A
($><<A+?>;c,r,_,x,y=gets.bytes.map{~-_1%8})until[[c,r,x,y]]-(m[0]?q[0]?q:m:(p E;Z))==[]
#$g,b[t[A]=x+8*y]=p,A"}

Try it online! (620 bytes because the older Ruby on TIO doesn't support numbered block parameters.) A simple example game is included that illustrates most of Quagmire's rules. Just for clarity, the input is commented and invalid moves are indented.

Now the main loop begins, running until one player wins. Following the 3 lines that display the board, the remainder of the program is enclosed in an eval string, which allows a few chunks of code (stored in the strings $f, $g, and h) to be recycled. The first 5 lines of this string contain the real meat of the program: the lambdas M and N.

Let's start with N. N finds the set of all squares that are connected to the square at co-ordinates o (co-ordinates are pairs of 0-indexed column and row numbers). Here, connected means that there is a path between all the squares that is not blocked by enemy pieces. Source and destination squares must be unconnected as a prerequisite for jumping an enemy piece. N fans out from the current square, adding its non-enemy neighbours (n=M[o]) and their neighbours (n|=n.flat_map{M[_1]) to the set n by calling M (see below). We redo this process, adding more neighbours of neighbours, until n no longer changes (i.e. until all connected squares have been found).

M probes the board around the square at co-ordinates u. Its exact behaviour is determined by the switch w, which takes a value of either nil (a.k.a. p) or 1:

  • When w is nil, M returns an array s of the co-ordinates of all neighbours (8-neighbourhood) of the square at u that do not contain enemy pieces (k!=E). Lambda N uses this information to find the set of all squares that are connected to the current square, as described above. The co-ordinates of each neighbour (column c and row r, stored as the pair v) are obtained by independently advancing c and r by -1, 0, or 1 ((0..8).map...(v=c+=_1/3-1,r+=_1%3-1)), ensuring that c and r both lie within the board ((v=...)-i==[]). This code for finding adjacent squares is required again later, so to save bytes it is stored as a string in $f and interpolated into the eval string. Similarly, the code that returns the value of the square at v is saved in $g ($g='b[c+8*r]').

  • When w equals 1 and the square at u contains one of the active player's pieces (b[z]==A), M does two things:

    1. It returns an array s containing all possible moves for the active player originating from this square.

    2. It saves the values of all neighbouring squares in Q, which is used later to test for quagmire.

    Moves are only allowed if the piece was not moved on the previous turn (t[A]!=z). For jumps, the destination square is found by advancing c and r in the same directions a second time (by interpolating $f) and testing whether this square is empty (!#$g). For a jump to be possible, there must either be an active piece to jump over (k==A) or, if an enemy piece is being jumped, the source and destination squares must be unconnected (k&&N[u]&N[v]==[]). For ordinary moves, c and r are reset (c,r=u) and then both variables are advanced in the same directions as before, one square at a time, until no further move along that path is possible (while#$f!#$g). Moves are saved (s<<u+v) in the format [c,r,x,y], that is, the column and row numbers of the source square followed by the column and row numbers of the destination.

  1. Loop over all squares of the board by forming pairs of column and row numbers (i.product(i)).

  2. For the current square, complete the following steps, all saved in h:

    • Call M in move-finding mode (M[_1,1]). If the square contains an enemy piece, update Q with the values of the neighbouring squares. (Any possible moves for that piece are also returned, but we immediately discard them: it's not the opponent's turn to move, after all!)

    • Interchange the active and enemy pieces (A,E=E,A). The reason for interchanging players is that quagmire checks are performed twice per square, once (here in step 2) to find whether the opponent has left a piece in quagmire and again (in step 4) to find whether the active player has been quagmired by the opponent's previous move.

    • Test whether the current square contains a quagmired enemy piece (([p,A]&Q)[0]). ([p,A]&Q)[0] is truthy only if there are no empty neighbours and at least one neighbour is an active piece.

  3. If the current square contains a quagmired enemy piece then the game is won. Print the active player's symbol (p A) and terminate by throwing a tantrum (Z is not defined).

  4. Repeat step 2, reading 'active' for 'enemy' and vice versa, but this time add all possible moves (neglecting quagmire for the moment) for the active player to m and keep a copy in e (m+=e=#{h}). If the current square contains a quagmired active piece then add e to q as well (q+=e).

  5. Outside the loop, interchange the active/enemy pieces to advance the turn (A,E=E,A).

  6. Finally it's time for some input!

    • We now have two lists of moves for the active player: m contains all possible moves (neglecting quagmire) whereas q (a subset of m) contains possible moves by any quagmired pieces. (m[0]?q[0]?q:m:(p E;Z)) decides which move list should be used. If m is empty (i.e. the active player has no possible moves) then the game is lost; print the opponent's symbol and terminate (m[0]?...:(p E;Z)). If q is nonempty it takes precedence over m (q[0]?q:m).

    • Until a valid move is entered correctly (until[[c,r,x,y]]-(...)==[]), display the prompt ($><<A+?>) and convert the input to 0-indexed column/row numbers (gets.bytes.map{~-_1%8}).

  7. Update the source and destination squares, saving the destination index in t (#$g,b[t[A]=x+8*y]=p,A).

deleted 452 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65

Ruby 2.7, 695...641625 625618 bytes

i=*0..7
t,*b={}
"?>=<765/.'".bytes{b[_1],b[~_1]=A=?X,E=?O}
loop{puts'
 '+[*?a..?h]*' ',a='+-'*8+?+,i.map{|j|"|#{b[8*~j,8].map{_1||?.}*?|}| #{8-j}
"+a}
eval"M,*m=->u,w=p,*s{Q=(0..8).map{c,r=u;x,y=u
#{h='f='(v=x+=_1/3-1,y+=_1%3-1)-i==[]&&'}(k=b[x+8*y]
w ?b[z=c+8*r]==A&&(t[A]!=z&&(#{f=h+'g=f+'!b[x+8*y]'}&&(k==A||k&&N[u]&N[v]==[])&&s<<[#{$g='c,r,w,x,y'}]&&s<<u+v
x,y=u;s<<[#$g]while#y=u;s<<u+v while#{fg});k):k!=E&&s<<v)};s}
N,*q=->o,n=M[o]{n==n|=n.flat_map{M[_1]}||redo;n}
i.product(i){#{d='M[_1h='M[_1,7];A1];A,E=E,A;([p,A]&Q)[0]&&'}(p A;Z);m+=e=#{dh}q+=e}
A,E=E,A
($><<A+?>;#$g=gets>;c,r,_,x,y=gets.bytes.map{~-_1%8})until[[#$g]]until[[c,r,x,y]]-(m[0]?q[0]?q:m:(p E;Z))==[]
b[c+8*r],b[t[A]=x+8*y]=p,A"}

Try it online!Try it online! (635628 bytes because the older Ruby on TIO doesn't support numbered block parameters.) A simple example game is included that illustrates most of Quagmire's rules. Just for clarity, the input is commented and invalid moves are indented.

Input is in the format x# y#, terminated either by newline or EOF. Any single byte is accepted as the separator. The input prompt is O> for player 1 and X> for player 2. (In an interactive terminal the input appears as it is typed, after the prompt.) The prompt is repeated until a valid move is entered, then the updated board is printed. When the game ends, the winning player's symbol is printed in double quotes; I like to imagine it is raising its arms in victory.

Now the main loop begins, running until one player wins. Following the 3 lines that display the board, the remainder of the program is enclosed in an eval string, which allows a few chunks of code (stored in the strings d, f, $gg, and h) to be recycled. The first 65 lines of this string contain the real meat of the program: the lambdas M and N.

M probes the board around the square at column c and row r, stored as the co-ordinate pair u. Its exact behaviour is determined by the switch w, which takes a value of either nil (a.k.a. p) or 71:

  • When w is nil, M returns an array s of the co-ordinates of all neighbours (8-neighbourhood) of the current square that do not contain enemy pieces (k!=E; k=b[x+8*y] is the value of the neighbouring square). Lambda N uses this information to find the set of all squares that are connected to the current square, as described above. The co-ordinates of each neighbour (column x and row y, stored as the co-ordinate pair v) are obtained by independently advancing c and r by -1, 0, or 1 ((0..8).map...(v=x+=_1/3-1,y+=_1%3-1)), ensuring that x and y both lie within the board ((v=...)-i==[]). The code for finding neighbours is required again later, so to save bytes it is stored as a string in hf and interpolated into the eval string.

  • When w equals 71 and the current square contains one of the active player's pieces (b[z=c+8*r]==A), M does two things:

    1. It returns an array s containing all possible moves for the active player originating from the current square.

    2. It saves the values of all neighbouring squares in Q, which is used later to test for quagmire.

    Moves are only allowed if the piece was not moved on the previous turn (t[A]!=z). For jumps, the destination square is found by advancing x and y in the same directions once again (by interpolating hf) and testing whether this square is empty (!b[x+8*y]). This code snippet is required later for ordinary moves and so is saved in fg. For a jump to be possible, there must either be an active piece to jump over (k==A) or, if an enemy piece is being jumped, the source and destination squares must be unconnected (k&&N[u]&N[v]==[]). For ordinary moves, x and y are reset (x,y=u) and then both variables are advanced in the same directions as before, one square at a time, until no further move along that path is possible (while#{fg}).

      Moves are saved in the format [c,r,w,x,y]. Including an entry for the separator allows us to avoid special handling later. w was chosen to be 7 because this corresponds to the ( s<<u+v (space) character underin the method used to process input (see below). (In fact, any character with a codepoint of 7 (modulo 8) will work.) The snippetformat c[c,r,w,x,y, which would otherwise appear four times in the code, is instead stored in $gy] and interpolated.

  1. Loop over all squares of the board by forming pairs of column and row indices (i.product(i)).

  2. For the current square, complete the following steps, all saved in dh:

    • Call M in move-finding mode (M[_1,7]1]). If the square contains an enemy piece, update Q with the values of the neighbouring squares. (Any possible moves for that piece are also returned, but we immediately discard them: it's not the opponent's turn to move, after all!)

    • Interchange the active and enemy pieces (A,E=E,A). The reason for interchanging players is that quagmire checks are performed twice per square, once (here in step 2) to find whether the opponent has left a piece in quagmire and again (in step 4) to find whether the active player has been quagmired by the opponent's previous move.

    • Test whether the current square contains a quagmired enemy piece (([p,A]&s)[0]). ([p,A]&s)[0] is truthy only if there are no empty neighbours and at least one neighbour is an active piece.

  3. If the current square contains a quagmired enemy piece then the game is won. Print the active player's symbol (p A) and terminate by throwing a tantrum (Z is not defined).

  4. Repeat step 2, reading 'active' for 'enemy' and vice versa, but this time add all possible moves (neglecting quagmire for the moment) for the active player to m and keep a copy in e (m+=e=#{dh}). If the current square contains a quagmired active piece then add e to q as well (q+=e).

  5. Outside the loop, interchange the active/enemy pieces to advance the turn (A,E=E,A).

  6. Finally it's time for some input!

    • We now have two lists of moves for the active player: m contains all possible moves (neglecting quagmire) whereas q (a subset of m) contains possible moves by any quagmired pieces. (m[0]?q[0]?q:m:(p E;Z)) decides which move list should be used. If m is empty (i.e. the active player has no possible moves) then the game is lost; print the opponent's symbol and terminate (m[0]?...:(p E;Z)). If q is nonempty it takes precedence over m (q[0]?q:m).

    • Until a valid move is entered correctly (until[[#$g]]until[[c,r,x,y]]-(...)==[]), display the prompt ($><<A+?>), convert the input to codepoints (gets.bytes), then convert these to 0-indexed column/row numbers (map{~-_1%8}). The separator ( ) gets mapped to 7 (recall that this was the value of w when the move list was generated).

  7. Update the source and destination squares, saving the destination index in t (b[c+8*r],b[t[A]=x+8*y]=p,A).

Ruby 2.7, 695...641 625 bytes

i=*0..7
t,*b={}
"?>=<765/.'".bytes{b[_1],b[~_1]=A=?X,E=?O}
loop{puts'
 '+[*?a..?h]*' ',a='+-'*8+?+,i.map{|j|"|#{b[8*~j,8].map{_1||?.}*?|}| #{8-j}
"+a}
eval"M,*m=->u,w=p,*s{Q=(0..8).map{c,r=u;x,y=u
#{h='(v=x+=_1/3-1,y+=_1%3-1)-i==[]&&'}(k=b[x+8*y]
w ?b[z=c+8*r]==A&&(t[A]!=z&&(#{f=h+'!b[x+8*y]'}&&(k==A||k&&N[u]&N[v]==[])&&s<<[#{$g='c,r,w,x,y'}]
x,y=u;s<<[#$g]while#{f});k):k!=E&&s<<v)};s}
N,*q=->o,n=M[o]{n==n|=n.flat_map{M[_1]}||redo;n}
i.product(i){#{d='M[_1,7];A,E=E,A;([p,A]&Q)[0]&&'}(p A;Z);m+=e=#{d}q+=e}
A,E=E,A
($><<A+?>;#$g=gets.bytes.map{~-_1%8})until[[#$g]]-(m[0]?q[0]?q:m:(p E;Z))==[]
b[c+8*r],b[t[A]=x+8*y]=p,A"}

Try it online! (635 bytes because the older Ruby on TIO doesn't support numbered block parameters.) A simple example game is included that illustrates most of Quagmire's rules. Just for clarity, the input is commented and invalid moves are indented.

Input is in the format x# y#, terminated either by newline or EOF. The input prompt is O> for player 1 and X> for player 2. (In an interactive terminal the input appears as it is typed, after the prompt.) The prompt is repeated until a valid move is entered, then the updated board is printed. When the game ends, the winning player's symbol is printed in double quotes; I like to imagine it is raising its arms in victory.

Now the main loop begins, running until one player wins. Following the 3 lines that display the board, the remainder of the program is enclosed in an eval string, which allows a few chunks of code (stored in the strings d, f, $g, and h) to be recycled. The first 6 lines of this string contain the real meat of the program: the lambdas M and N.

M probes the board around the square at column c and row r, stored as the co-ordinate pair u. Its exact behaviour is determined by the switch w, which takes a value of either nil (a.k.a. p) or 7:

  • When w is nil, M returns an array s of the co-ordinates of all neighbours (8-neighbourhood) of the current square that do not contain enemy pieces (k!=E; k=b[x+8*y] is the value of the neighbouring square). Lambda N uses this information to find the set of all squares that are connected to the current square, as described above. The co-ordinates of each neighbour (column x and row y, stored as the co-ordinate pair v) are obtained by independently advancing c and r by -1, 0, or 1 ((0..8).map...(v=x+=_1/3-1,y+=_1%3-1)), ensuring that x and y both lie within the board ((v=...)-i==[]). The code for finding neighbours is required again later, so to save bytes it is stored as a string in h and interpolated into the eval string.

  • When w equals 7 and the current square contains one of the active player's pieces (b[z=c+8*r]==A), M does two things:

    1. It returns an array s containing all possible moves for the active player originating from the current square.

    2. It saves the values of all neighbouring squares in Q, which is used later to test for quagmire.

    Moves are only allowed if the piece was not moved on the previous turn (t[A]!=z). For jumps, the destination square is found by advancing x and y in the same directions once again (by interpolating h) and testing whether this square is empty (!b[x+8*y]). This code snippet is required later for ordinary moves and so is saved in f. For a jump to be possible, there must either be an active piece to jump over (k==A) or, if an enemy piece is being jumped, the source and destination squares must be unconnected (k&&N[u]&N[v]==[]). For ordinary moves, x and y are reset (x,y=u) and then both variables are advanced in the same directions as before, one square at a time, until no further move along that path is possible (while#{f}).

      Moves are saved in the format [c,r,w,x,y]. Including an entry for the separator allows us to avoid special handling later. w was chosen to be 7 because this corresponds to the (space) character under the method used to process input (see below). (In fact, any character with a codepoint of 7 (modulo 8) will work.) The snippet c,r,w,x,y, which would otherwise appear four times in the code, is instead stored in $g and interpolated.

  1. Loop over all squares of the board by forming pairs of column and row indices (i.product(i)).

  2. For the current square, complete the following steps, all saved in d:

    • Call M in move-finding mode (M[_1,7]). If the square contains an enemy piece, update Q with the values of the neighbouring squares. (Any possible moves for that piece are also returned, but we immediately discard them: it's not the opponent's turn to move, after all!)

    • Interchange the active and enemy pieces (A,E=E,A). The reason for interchanging players is that quagmire checks are performed twice per square, once (here in step 2) to find whether the opponent has left a piece in quagmire and again (in step 4) to find whether the active player has been quagmired by the opponent's previous move.

    • Test whether the current square contains a quagmired enemy piece (([p,A]&s)[0]). ([p,A]&s)[0] is truthy only if there are no empty neighbours and at least one neighbour is an active piece.

  3. If the current square contains a quagmired enemy piece then the game is won. Print the active player's symbol (p A) and terminate by throwing a tantrum (Z is not defined).

  4. Repeat step 2, reading 'active' for 'enemy' and vice versa, but this time add all possible moves (neglecting quagmire for the moment) for the active player to m and keep a copy in e (m+=e=#{d}). If the current square contains a quagmired active piece then add e to q as well (q+=e).

  5. Outside the loop, interchange the active/enemy pieces to advance the turn (A,E=E,A).

  6. Finally it's time for some input!

    • We now have two lists of moves for the active player: m contains all possible moves (neglecting quagmire) whereas q (a subset of m) contains possible moves by any quagmired pieces. (m[0]?q[0]?q:m:(p E;Z)) decides which move list should be used. If m is empty (i.e. the active player has no possible moves) then the game is lost; print the opponent's symbol and terminate (m[0]?...:(p E;Z)). If q is nonempty it takes precedence over m (q[0]?q:m).

    • Until a valid move is entered correctly (until[[#$g]]-(...)==[]), display the prompt ($><<A+?>), convert the input to codepoints (gets.bytes), then convert these to 0-indexed column/row numbers (map{~-_1%8}). The separator ( ) gets mapped to 7 (recall that this was the value of w when the move list was generated).

  7. Update the source and destination squares, saving the destination index in t.

Ruby 2.7, 695...625 618 bytes

i=*0..7
t,*b={}
"?>=<765/.'".bytes{b[_1],b[~_1]=A=?X,E=?O}
loop{puts'
 '+[*?a..?h]*' ',a='+-'*8+?+,i.map{|j|"|#{b[8*~j,8].map{_1||?.}*?|}| #{8-j}
"+a}
eval"M,*m=->u,w=p,*s{Q=(0..8).map{c,r=u;x,y=u
#{f='(v=x+=_1/3-1,y+=_1%3-1)-i==[]&&'}(k=b[x+8*y]
w ?b[z=c+8*r]==A&&(t[A]!=z&&(#{g=f+'!b[x+8*y]'}&&(k==A||k&&N[u]&N[v]==[])&&s<<u+v
x,y=u;s<<u+v while#{g});k):k!=E&&s<<v)};s}
N,*q=->o,n=M[o]{n==n|=n.flat_map{M[_1]}||redo;n}
i.product(i){#{h='M[_1,1];A,E=E,A;([p,A]&Q)[0]&&'}(p A;Z);m+=e=#{h}q+=e}
A,E=E,A
($><<A+?>;c,r,_,x,y=gets.bytes.map{~-_1%8})until[[c,r,x,y]]-(m[0]?q[0]?q:m:(p E;Z))==[]
b[c+8*r],b[t[A]=x+8*y]=p,A"}

Try it online! (628 bytes because the older Ruby on TIO doesn't support numbered block parameters.) A simple example game is included that illustrates most of Quagmire's rules. Just for clarity, the input is commented and invalid moves are indented.

Input is in the format x# y#, terminated either by newline or EOF. Any single byte is accepted as the separator. The input prompt is O> for player 1 and X> for player 2. (In an interactive terminal the input appears as it is typed, after the prompt.) The prompt is repeated until a valid move is entered, then the updated board is printed. When the game ends, the winning player's symbol is printed in double quotes; I like to imagine it is raising its arms in victory.

Now the main loop begins, running until one player wins. Following the 3 lines that display the board, the remainder of the program is enclosed in an eval string, which allows a few chunks of code (stored in the strings f, g, and h) to be recycled. The first 5 lines of this string contain the real meat of the program: the lambdas M and N.

M probes the board around the square at column c and row r, stored as the co-ordinate pair u. Its exact behaviour is determined by the switch w, which takes a value of either nil (a.k.a. p) or 1:

  • When w is nil, M returns an array s of the co-ordinates of all neighbours (8-neighbourhood) of the current square that do not contain enemy pieces (k!=E; k=b[x+8*y] is the value of the neighbouring square). Lambda N uses this information to find the set of all squares that are connected to the current square, as described above. The co-ordinates of each neighbour (column x and row y, stored as the co-ordinate pair v) are obtained by independently advancing c and r by -1, 0, or 1 ((0..8).map...(v=x+=_1/3-1,y+=_1%3-1)), ensuring that x and y both lie within the board ((v=...)-i==[]). The code for finding neighbours is required again later, so to save bytes it is stored as a string in f and interpolated into the eval string.

  • When w equals 1 and the current square contains one of the active player's pieces (b[z=c+8*r]==A), M does two things:

    1. It returns an array s containing all possible moves for the active player originating from the current square.

    2. It saves the values of all neighbouring squares in Q, which is used later to test for quagmire.

    Moves are only allowed if the piece was not moved on the previous turn (t[A]!=z). For jumps, the destination square is found by advancing x and y in the same directions once again (by interpolating f) and testing whether this square is empty (!b[x+8*y]). This code snippet is required later for ordinary moves and so is saved in g. For a jump to be possible, there must either be an active piece to jump over (k==A) or, if an enemy piece is being jumped, the source and destination squares must be unconnected (k&&N[u]&N[v]==[]). For ordinary moves, x and y are reset (x,y=u) and then both variables are advanced in the same directions as before, one square at a time, until no further move along that path is possible (while#{g}). Moves are saved (s<<u+v) in the format [c,r,x,y].

  1. Loop over all squares of the board by forming pairs of column and row indices (i.product(i)).

  2. For the current square, complete the following steps, all saved in h:

    • Call M in move-finding mode (M[_1,1]). If the square contains an enemy piece, update Q with the values of the neighbouring squares. (Any possible moves for that piece are also returned, but we immediately discard them: it's not the opponent's turn to move, after all!)

    • Interchange the active and enemy pieces (A,E=E,A). The reason for interchanging players is that quagmire checks are performed twice per square, once (here in step 2) to find whether the opponent has left a piece in quagmire and again (in step 4) to find whether the active player has been quagmired by the opponent's previous move.

    • Test whether the current square contains a quagmired enemy piece (([p,A]&s)[0]). ([p,A]&s)[0] is truthy only if there are no empty neighbours and at least one neighbour is an active piece.

  3. If the current square contains a quagmired enemy piece then the game is won. Print the active player's symbol (p A) and terminate by throwing a tantrum (Z is not defined).

  4. Repeat step 2, reading 'active' for 'enemy' and vice versa, but this time add all possible moves (neglecting quagmire for the moment) for the active player to m and keep a copy in e (m+=e=#{h}). If the current square contains a quagmired active piece then add e to q as well (q+=e).

  5. Outside the loop, interchange the active/enemy pieces to advance the turn (A,E=E,A).

  6. Finally it's time for some input!

    • We now have two lists of moves for the active player: m contains all possible moves (neglecting quagmire) whereas q (a subset of m) contains possible moves by any quagmired pieces. (m[0]?q[0]?q:m:(p E;Z)) decides which move list should be used. If m is empty (i.e. the active player has no possible moves) then the game is lost; print the opponent's symbol and terminate (m[0]?...:(p E;Z)). If q is nonempty it takes precedence over m (q[0]?q:m).

    • Until a valid move is entered correctly (until[[c,r,x,y]]-(...)==[]), display the prompt ($><<A+?>), convert the input to codepoints (gets.bytes), then convert these to 0-indexed column/row numbers (map{~-_1%8}).

  7. Update the source and destination squares, saving the destination index in t (b[c+8*r],b[t[A]=x+8*y]=p,A).

deleted 4 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
i=*0..7
t,*b={}
"?>=<765/.'".bytes{b[_1],b[~_1]=A=?X,E=?O}
loop{puts'
 '+[*?a..?h]*' ',a='+-'*8+?+,i.map{|j|"|#{b[8*~j,8].map{_1||?.}*?|}| #{8-j}
"+a}
eval"M,*m=->u,w=p,*s{Q=(0..8).map{c,r=u;x,y=u
#{h='(v=x+=_1/3-1,y+=_1%3-1)-i==[]&&'}(k=b[x+8*y]
k!=E&&s<<v
w&&b[z=c+8*r]==A&&w ?b[z=c+8*r]==A&&(t[A]!=z&&(#{f=h+'!b[x+8*y]'}&&(k==A||k&&N[u]&N[v]==[])&&s<<[#{$g='c,r,w,x,y'}]
x,y=u;s<<[#$g]while#{f});k):k!=E&&s<<v)};s}
N,*q=->o,n=M[o]{n==n|=n.flat_map{M[_1]}||redo;n}
i.product(i){#{d='M[_1,7];A,E=E,A;([p,A]&Q)[0]&&'}(p A;Z);m+=e=#{d}q+=e}
A,E=E,A
($><<A+?>;#$g=gets.bytes.map{~-_1%8})until[[#$g]]-(m[0]?q[0]?q:m:(p E;Z))==[]
b[c+8*r],b[t[A]=x+8*y]=p,A"}

Try it online!Try it online! (635 bytes because the older Ruby on TIO doesn't support numbered block parameters.) A simple example game is included that illustrates most of Quagmire's rules. Just for clarity, the input is commented and invalid moves are indented.

i=*0..7
t,*b={}
"?>=<765/.'".bytes{b[_1],b[~_1]=A=?X,E=?O}
loop{puts'
 '+[*?a..?h]*' ',a='+-'*8+?+,i.map{|j|"|#{b[8*~j,8].map{_1||?.}*?|}| #{8-j}
"+a}
eval"M,*m=->u,w=p,*s{Q=(0..8).map{c,r=u;x,y=u
#{h='(v=x+=_1/3-1,y+=_1%3-1)-i==[]&&'}(k=b[x+8*y]
k!=E&&s<<v
w&&b[z=c+8*r]==A&&(t[A]!=z&&(#{f=h+'!b[x+8*y]'}&&(k==A||k&&N[u]&N[v]==[])&&s<<[#{$g='c,r,w,x,y'}]
x,y=u;s<<[#$g]while#{f});k))};s}
N,*q=->o,n=M[o]{n==n|=n.flat_map{M[_1]}||redo;n}
i.product(i){#{d='M[_1,7];A,E=E,A;([p,A]&Q)[0]&&'}(p A;Z);m+=e=#{d}q+=e}
A,E=E,A
($><<A+?>;#$g=gets.bytes.map{~-_1%8})until[[#$g]]-(m[0]?q[0]?q:m:(p E;Z))==[]
b[c+8*r],b[t[A]=x+8*y]=p,A"}

Try it online! (635 bytes because the older Ruby on TIO doesn't support numbered block parameters.) A simple example game is included that illustrates most of Quagmire's rules. Just for clarity, the input is commented and invalid moves are indented.

i=*0..7
t,*b={}
"?>=<765/.'".bytes{b[_1],b[~_1]=A=?X,E=?O}
loop{puts'
 '+[*?a..?h]*' ',a='+-'*8+?+,i.map{|j|"|#{b[8*~j,8].map{_1||?.}*?|}| #{8-j}
"+a}
eval"M,*m=->u,w=p,*s{Q=(0..8).map{c,r=u;x,y=u
#{h='(v=x+=_1/3-1,y+=_1%3-1)-i==[]&&'}(k=b[x+8*y]
w ?b[z=c+8*r]==A&&(t[A]!=z&&(#{f=h+'!b[x+8*y]'}&&(k==A||k&&N[u]&N[v]==[])&&s<<[#{$g='c,r,w,x,y'}]
x,y=u;s<<[#$g]while#{f});k):k!=E&&s<<v)};s}
N,*q=->o,n=M[o]{n==n|=n.flat_map{M[_1]}||redo;n}
i.product(i){#{d='M[_1,7];A,E=E,A;([p,A]&Q)[0]&&'}(p A;Z);m+=e=#{d}q+=e}
A,E=E,A
($><<A+?>;#$g=gets.bytes.map{~-_1%8})until[[#$g]]-(m[0]?q[0]?q:m:(p E;Z))==[]
b[c+8*r],b[t[A]=x+8*y]=p,A"}

Try it online! (635 bytes because the older Ruby on TIO doesn't support numbered block parameters.) A simple example game is included that illustrates most of Quagmire's rules. Just for clarity, the input is commented and invalid moves are indented.

edited body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
Bounty Ended with 100 reputation awarded by caird coinheringaahin g
deleted 166 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
added 2 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
added 3 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
added 3 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
added 105 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
added 121 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
deleted 1 character in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
added 209 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
deleted 5 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
added 123 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
added 727 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
deleted 39 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
added 10 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
edited body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
added 169 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
deleted 110 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
deleted 49 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
added 502 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
added 4 characters in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading
deleted 1 character in body
Source Link
Dingus
  • 11.3k
  • 2
  • 31
  • 65
Loading