# [Ruby 2.7], <sup><s>695...625</s></sup> 618 bytes

<!-- language-all: lang-ruby -->

    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!][TIO-ksk40bev] (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.

[Ruby 2.7]: https://www.ruby-lang.org/
[TIO-ksk40bev]: https://tio.run/##dVRbd9o4EH5e/YoJtPgmnBISYOMoPuye7Fva07eedb2tjWUQYNlYNgSw89ezYy5tshf7HJ3RzKdvbhrlZbh9aYMqcpGBkBGXBQQygkmaJCgriPM0QUNWFuQPseT2JhcF1zWhUfg@CQqwDza7EOl3e6rKUL/866syLylomnFS4L5tn1VqVXK@43rrq2wZBnmnikhIO@dpxmVDa7wIZn6w7SEpqBmyfU1a7j27Gw5uLm2tZYfbgqt9Na9Cb@7T0Hue@2zM3C/0gbmfarJM02yP8SiNgGZ5phvYtjvzTQ00GjDN6mrmyHItKuwkyBqaVtXeh97IfJ7TkX9ULvCvXLs23aquoL0fdecYhBXUhK@DZeuRmgnr3pd0wzJqqv1npmO4I@PMOKE5K50numUlae9jpulr9mSx@WW/26NbFN6jYHQFY57f6Wi1vmCh92SNzK1PNuCG3o5NcJf7jI07Hb3wxv4F26HU3k9ZbGkXZ7RWo3KBqKpadDofvdLHZe03vEano@7uSmtNDnE4xw1sZthBpKkNZ2HcLi7YwwG3NmpH1eQjNVeYWEole/RSfy8ZkxWTdrwMim@H5HbVo7fz66rKeZQ6sibCzvI0KieFLozG3N7PmIYY2vOdMbbkgY4d3cvo2O98NrwPx3wzGDt/Gk5iMc7wQL1CoSYnONHf3d/djS333sFC0m@0SWDKC3Xs/LnIz935@1FtlLIQS89rkIjz/a6eoBd3dVhuk1v09YC@jKYmJPSOdcVb0xSVHauITRy36pcXgKgHkx40XxsmgdQKWDaTkEpINxIywSecHFCzm/9EccmT7Wsc759wMsWhamYsENMZHhDyhIh@/eWIiLgqhAxwitAbXl8RcQjTII@OuNPXML2GovHpagD94fi339EYBhHEaZ4EBRr4CPjbbJJ0zf8RJCL48Cf1QQ041LxHwiuIBwiZDmB6znc6BKFgVQbTROAVODwUSamO1A12CPxtbeZlkoEScrp86xqR0xs8EQ8gvvl3lCpI@CmaYiNwFRILmKcbEvRh0ifxCMJrElxD0CcYXXRF2mEfMOQTlYihlKcnjEe06SuXkYKizGXDdU7BgS@wEVKRpquY8/UPDiQ5xC5kkaLryTJVmHDzvEAav05FOc2Z/2H/dGD/Gw "Ruby – Try It Online"

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.

# Explanation

> While perhaps not a brilliant board game, it ought to be a fun code golf!

#### Steel your Brain

The first 3 lines perform initialisation. `i` is a fixed list of row/column numbers. The value of each square on the board (`O`, `X`, or `nil`) is stored in the (1D) array `b`. Empty squares, stored as `nil`, are replaced by `.` on output. The starting configuration is encoded by the 10-byte string `?>=<765/.'`, whose codepoints give the indices of squares containing X's. `t` is a hash that stores the index of the piece moved on each player's previous turn (this piece cannot be moved on the next turn). `A` and `E` store the 'active' and 'enemy' piece symbols and are interchanged each turn. 

Being *constants* (uppercase names), `A` and `E` have global scope. Constants are abused throughout the program to avoid scoping issues that would arise with ordinary local variables. A side effect is that whenever a constant is reassigned, warnings are emitted to STDERR. (These warnings may be suppressed by calling Ruby with the `-W0` option.)

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`.

#### Mary had a little lambda

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.

    1. 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]`.

#### Into the quagmire

The last four lines of the program run the game according to the following steps:

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.

1. 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).

1. 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`).

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

1. 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}`).

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