The game of xiangqi, also known as Chinese chess, is a chess-like game popular in China, Vietnam, Taiwan, and other East Asian countries. The colours of the two sides in xiangqi are red and black. There are seven pieces in xiangqi: the general (G), advisor (A), elephant (E), horse (H), chariot (R), cannon (C), and soldier (S). For the purposes of this challenge, uppercase pieces are considered red and lowercase pieces black. Most of these pieces have a rough equivalent in Western chess, but there is one completely unique piece: the cannon.

The cannon moves like a rook in chess or a chariot in xiangqi (moving any number of spaces on either the X or Y axes), but cannot attack this way. Instead, it attacks by jumping along the X or Y axes (the same way it moves) over one piece of any colour (friend or foe) and landing on the opposite-coloured piece, which it then captures. Note that like all chess and xiangqi pieces, cannons cannot capture pieces of their own colour.

For example, in the following diagram, the spaces to which the cannon (C) can move are marked with *, and those to which it can jump and capture are marked with X, assuming that there is a black/lowercase piece there.



Write a program or function that, given a xiangqi board and the coordinates of a cannon on that board as input, outputs a list of coordinates to which the cannon can move or jump.

The format for all I/O is flexible.

Acceptable formats for the xiangqi board include a newline-separated string, a list of strings, or a string with any other separator not in aceghrsACEGHRS.. You may assume the board will always be 9x10, the size of a xiangqi board.

The contents of the board itself will consist of a number of periods (.), representing empty points on the board, and characters representing pieces. The piece-to-character mapping is as follows:

A -> advisor
C -> cannon
E -> elephant
G -> general
H -> horse
R -> chariot
S -> soldier

Uppercase letters represent red pieces, and lowercase letters represent black pieces. Characters not listed here (i.e. not in aceghrsACEGHRS.) will not appear in the board.

The format of the input coordinate is flexible, and is not required to match the format of the output coordinates. It can be a list of two integer elements, a 2-tuple, two numbers with any separator, or two characters, for example. It may also be either 0-indexed or 1-indexed. You may assume that the coordinate on the board will always resolve to a cannon (C or c).

The coordinates to which the cannon can jump and move must appear in the same list in output; distinction between the two is not necessary. Acceptable formats for any individual output coordinates are the same as those for the input coord. The coordinates may be newline-separated, output as a list, or any other representation. No particular order is necessary; the order does not even have to be deterministic.

Note that jumps onto a piece of the same colour (case) of the cannon are not legal, and thus cannot appear in the output.

Test cases

Note that not all of the test cases are possible xiangqi positions.

Input board
Input coordinate (0-indexed)
List of output coordinates

(4, 5)
[(0, 5), (1, 5), (2, 5), (3, 5), (5, 5), (6, 5), (7, 5), (8, 5), (4, 0), (4, 1), (4, 2), (4, 3), (4, 4), (4, 6), (4, 7), (4, 8), (4, 9)]

(5, 7)
[(4, 7), (6, 7), (7, 7), (5, 6), (5, 5), (5, 1)]

(2, 3)
[(2, 0), (2, 2), (4, 3), (2, 8)]

(7, 7)
[(2, 7), (3, 7), (4, 7), (5, 7), (6, 7), (8, 7), (7, 0), (7, 3), (7, 4), (7, 5), (7, 6), (7, 8)]


This is , so the shortest answer (in bytes) wins. Happy golfing!

  • 4
    \$\begingroup\$ +1 for Xiangqi. It is an awesome game, that I had the opportunity to learn from my colleagues when I worked for a Chinese company. The general feel is just like playing chess (keep a sharp eye for defense but play aggressively) but the tactics are different (it's good to bring the chariots out early, whereas it's bad to bring the rooks out early in chess.) en.wikipedia.org/wiki/Xiangqi \$\endgroup\$ Sep 10, 2016 at 20:27
  • \$\begingroup\$ @LevelRiverSt it's much less technical than chess and feels like a videogame with a different strategic feel. I like it! \$\endgroup\$ Sep 10, 2016 at 23:36
  • \$\begingroup\$ Seems there may be no king, but can there be more than possible piece? \$\endgroup\$
    – l4m2
    May 4, 2018 at 9:04

3 Answers 3


Pip, 112 + 1 = 113 bytes

Takes input as command-line arguments: the two coordinates, followed by the 10 lines of the board. Coordinates are 0-based. Outputs coordinates like 67 77, with a newline between the same-row list and the same-column list. One byte added for the -s flag.

g@>:2P(f:{Ya@bQ'C?X^z;X^AZa@b:'@aR`\.*@\.*`{aR'.s}Ry.`\.*\w *@`s._@>1R`@ *\w\.*`.y_@<v.s@*s}g@ba).ba.(f;J(Zga)b)

Try it online!

Explanation of somewhat ungolfed version

 Y a@bQ'C ? `[a-z]` `[A-Z]`
 aR:`\.*@\.*` {aR'.s}
 aR:y.`\.*\w *@` s._@>1
 aR:`@ *\w\.*`.y _@<v.s
P (f g@b a).b
a.(f; J(Zg)@a b)

By default, Pip reads command-line args into the list g. It also stores the first five arguments in the variables a through e. Our first two arguments, a and b, are the coordinates of the cannon; g contains the coordinates followed by the rows of the board. To get just the board in g, we slice it from index 2 onward and assign back to g (g@>:2).

Now we define a function f. This function takes two arguments: a string representing a row or column of the board, and the index of the cannon in that string. These arguments are available inside the function as a and b. The function will return a list of all indices that represent spots the cannon can move to or capture.

First, we test whether a@b is C or c. If it's C, we're going to want the regex [a-z] to match the pieces it can capture. If it's c, the regex is [A-Z]. (The golfed code generates these regexes from the builtin variables for lower- and uppercase alphabet.) We Yank the appropriate regex into the y variable.

We change the cannon's character in the string to @ (to distinguish it from other cannons in the same row/column).

Next comes a series of regex replacements, which will change every place the cannon can go to a space character. The first regex \.*@\.* matches @ surrounded by any number of periods, all of which represent empty spots to which the cannon can move. The replacement uses a callback function {aR'.s} to change all the periods to spaces.

The next regex matches a piece the cannon can capture: either [a-z] or [A-Z] (depending on which one was yanked into y earlier) followed by \.*\w *@ (any number of periods, one letter, any number of spaces, and @). This will match a string like h..R @ (provided the cannon was C). The callback function s._@>1 slices the first character off and prepends a space.

The third regex is similar, but matches a capturable piece after the cannon rather than before it.

Finally, the function returns a@*s, using the find-all operator to get a list of the indices of all spaces.

We now call f on the cannon's row and again on the cannon's column. The row's string is g@b, with the cannon's index inside the string being a. The function returns a list of column numbers, to each of which we append the row number b. When the list is printed, the -s flag puts a space between the coordinate pairs.

To get the column, we use the Zip operator to transpose g, select index a, and Join the resulting list of characters into a string. The cannon's index within this string is b. The function returns a list of row numbers, to each of which we prepend the column number a. This list, being the last expression in the program, is autoprinted.

(In case anyone was wondering, the expression separator ; is there to force J to parse as a unary operator rather than binary.)


Python3, 309 bytes:

def f(t,b):
 q=[(*t,0,b[t[0]][t[1]],*i)for i in[(0,1),(0,-1),(1,0),(-1,0)]]
 while q:
  if 0<=(j:=x+X)<len(b)and 0<=(k:=y+Y)<len(b[0])and d<2:
   if'.'==(S:=b[j][k])and d==0:yield(j,k)
   if'.'!=S and S.isupper()!=c.isupper()and d:yield(j,k);d=2
   q+=[(j,k,[d,1][d<2 and'.'!=S],c,X,Y)]

Try it online!


Python 2, 238 bytes

def f(b,r,c,R=range):
 for K in[[(i,c)for i in I]for I in R(r+1,10),R(r)[::-1]]+[[(r,j)for j in J]for J in R(c+1,9),R(c)[::-1]]:
  for i,j in K:
	if(p<1)+Z*((b[r][c]<'c')!=('a'>B)):print i,j
	if Z:break

Try it online!

Ungolfed (301 bytes)

def g(a,b,I):
 for i,j in I:
  if p:
  if p<1:print(i,j)
def f(b,r,c):
 for I in[range(r+1,10),range(r-1,-1,-1)]:g(a,b,[(i,c)for i in I])
 for J in[range(c+1,9),range(c-1,-1,-1)]:g(a,b,[(r,j)for j in J])

Try it online!


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.