5
$\begingroup$

I'm building a boardgame...game in Mathematica. I have the board state represented as a matrix. It's a two player game, and each player (let's call them A & B) only has one type of piece or 'marker':

0 0 0 0 0 0 
0 A A B 0 0
0 A A B 0 0
0 B A A 0 0
0 0 0 0 0 0
0 0 0 0 0 0

I want to include an edit/board setup mode. For reasons that have to do with rules/mechanics of the game itself, when the user selects a space to clear it (set to 0), I need to check the 8 adjacent spaces around it and clear those as well if they contain the same player's markers. For example, if the user clicked the 'A' in the 3rd row and column in the above matrix, I want the surrounding As to automatically get set to 0 as well, leaving the Bs alone. Is it possible to write this logic for checking the 8 adjacent spaces without using repetitive If statements? E.g.:

clicked=board[[row,col]]; 
If[board[[row-1,col-1]]==clicked,board[[row-1,col-1]]=0];
If[board[[row,col-1]]==clicked,board[[row,col-1]]=0];
If[board[[row+1,col-1]]==clicked,board[[row+1,col-1]]=0];
.
. 
.
If[board[[row+1,col+1]]==clicked,board[[row-1,col-1]]=0]
board[[row,col]]=0;

It'd only be about 10 lines of code but it sure is ugly and a pain in the neck when it comes to readability/maintainability etc. Is there a better way to do it?

Edit: Yes, row and col can be the first and last rows and columns of the board matrix. That is something important I forgot to mention. Representing A and B pretty much the same in my real code. Just using LR (left right) and UD (up down) for the player's names.

$\endgroup$
  • $\begingroup$ How do you actually represent A and B in your code? $\endgroup$ – Martin Ender Apr 27 '16 at 8:26
  • $\begingroup$ Can row or col be the first or last row or column of the board? $\endgroup$ – Martin Ender Apr 27 '16 at 8:30
1
$\begingroup$

Completely different approach, so I'm making it a separate answer:

board[[row - 1 ;; row + 1, col - 1 ;; col + 1]] = 
  board[[row - 1 ;; row + 1, col - 1 ;; col + 1]] /. clicked -> 0;

This has the advantage of only updating the neighbourhood of the clicked cell instead of mapping a function over the entire grid. I also think it's quite clear and readable. Here is how it works, with the example of row = col = 4. We extract the neighbourhood of the clicked cell:

{{a, b, 0},
 {a, a, 0},
 {0, 0, 0}}

Then we replace the value that was clicked with 0:

{{0, b, 0},
 {0, 0, 0},
 {0, 0, 0}}

And then we write this patch of the board back into the correct position:

{{0, 0, 0, 0, 0, 0}, 
 {0, a, a, b, 0, 0}, 
 {0, a, 0, b, 0, 0}, 
 {0, b, 0, 0, 0, 0}, 
 {0, 0, 0, 0, 0, 0}, 
 {0, 0, 0, 0, 0, 0}}

Note that this code, like yours, will fail if the clicked cell is on the boundary of the grid, but this can be fixed via some Min and Max operations on the grid indices. In that case, you'll probably want to save the Spans in two variables so you don't have to write them twice.

$\endgroup$
  • $\begingroup$ Thanks for the replies and upvotes everyone, I ended up going with this. RangeR = Max[(MoveR - 1), 1] ;; Min[GridSize, (MoveR + 1)]; RangeC = Max[(MoveC - 1), 1] ;; Min[GridSize, (MoveC + 1)]; Board[[RangeR, RangeC]] = Board[[RangeR, RangeC]] /. LR -> 0; $\endgroup$ – Eriek Apr 28 '16 at 4:02
4
$\begingroup$

Array size/shape agnostic, takes care of edge cases automagically, call with player identifier, position, and current array, returns changed array:

fn = With[{cv = #1, cp = #2, cm = #3}, 
    ReplacePart[cm, Select[Position[cm, cv], ChessboardDistance[#, cp] <= 1 &] -> 0]] &;

Use example:

fn[a, {5, 3}, mat]
$\endgroup$
2
$\begingroup$

Here is one way to do it, although I feel like there is probably an even more elegant solution:

MapIndexed[
 If[
   # === clicked 
     && ChessboardDistance[#2, {row, col}] <= 1, 
   0, 
   #
  ] &, 
  board, 
  {2}
]

E.g. with row = col = 4 on your above example you'd get:

{{0, 0, 0, 0, 0, 0}, 
 {0, a, a, b, 0, 0}, 
 {0, a, 0, b, 0, 0}, 
 {0, b, 0, 0, 0, 0}, 
 {0, 0, 0, 0, 0, 0}, 
 {0, 0, 0, 0, 0, 0}}

MapIndexed is like Map except that the mapped function takes a second parameter which is a list of indices of the current element. We simply check whether that index is in the Moore neighbourhood of distance 1 from {row, col} in addition to the equality check with clicked.

Of course, this is somewhat less efficient than your current method for large boards, since the function is mapped over the entire board. If you're working with huge boards, this is probably not ideal. Otherwise, the runtime is going to be negligible either way.

$\endgroup$
2
$\begingroup$
ClearAll[fn2]
fn2 =With[{cp = #1, p = Position[#2, #2[[## & @@ #]]]}, 
        MapAt[0 &, #2, Select[p, ChessboardDistance[#, cp] <= 1 &]]] &

Row[MatrixForm /@ {board, fn2[{3, 3}, board], fn2[{3, 4}, board]}]

Mathematica graphics

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.