Read, Set, Go! How to Create a GO Board Game in Python

The board game of Go is so expressly Japanese – elegant, yet simple, yet layered with depths of genius for those who meditate upon it. The rules are simple, and well documented here.

Here is an example of a python program that will allow two human players to play against one another on one computer, providing an ascii display. In future posts we will be exploring how basic user-raw_input-dependent programs like this one can be improved through GUI programming and over-the-net communication – turning this one-computer ascii program into a graphical game you can play with your friend in Japan – or, if you’re already in Japan, with your friend in Jamaica (or, if you’re already in Jamaica, somewhere that is not Jamaica). I have explained the code in the comments.

In a recent post, I provided an example of AI that learns how to play tic-tac-toe, even until it becomes unbeatable. It may be worthwhile to consider how the approach shown there can be applied here.

 ## The number of spots per side of the board ## This code allows for an nxn board boardsize = 5 ## Value determining whether the player wants to quit or not gameon = 1 ## Lists of groups that have been removed from the board via capture, ## held in these varaibles in case, when all captures have been ## completed, the board resembles a previous game state and ## the move is invalid.  In that case, the groups are restored ## from these varaibles. restore_o = [] restore_x = []  ## Generates blank game states def initalize():     gs = []     for i in range(0,boardsize):         gs.append([])         for j in range(0,boardsize):             gs[i].append('-')     return gs  ## Provides an ascii display of the Go board def printboard(gs):     global boardsize     for row in gs:         rowprint = ''         for element in row:             rowprint += element             rowprint += ' '         print(rowprint)  ## Returns a list of the board positions surrounding the ## passed group. def gperm(group):     permimeter = []     global boardsize     hit = 0     loss = 0     ## Adds permimeter spots below     ## Works by looking from top to bottom, left to right,     ## at each posisition on the board.  When a posistion     ## is hit that is in the given group, I set hit = 1.     ## Then, at the next position that is not in that group,     ## or if the end of the column is reached, I set loss = 1.     ## That point is the first point below a point in that group,     ## so it is part of the permieter of that group.     i = 0     j = 0     while i < boardsize:         j = 0         hit = 0         while j < boardsize:             if [i,j] in group:                 hit = 1             elif (hit == 1) & ([i,j] not in group):                 loss = 1             if (hit == 1) & (loss == 1):                 permimeter.append([i,j])                 hit = 0                 loss = 0             j += 1         i += 1     ## Adds permimeter spots to the right     i = 0     j = 0     while i < boardsize:         j = 0         hit = 0         while j < boardsize:             if [j,i] in group:                 hit = 1             elif (hit == 1) & ([j,i] not in group):                 loss = 1             if (hit == 1) & (loss == 1):                 permimeter.append([j,i])                 hit = 0                 loss = 0             j += 1         i += 1     ## Adds permimeter spots above     i = 0     j = boardsize-1     while i < boardsize:         j = boardsize-1         hit = 0         while j >= 0:             if [i,j] in group:                 hit = 1             elif (hit == 1) & ([i,j] not in group):                 loss = 1             if (hit == 1) & (loss == 1):                 permimeter.append([i,j])                 hit = 0                 loss = 0             j -= 1         i += 1     ## Adds permimeter spots to the left     i = 0     j = boardsize-1     while i < boardsize:         j = boardsize-1         hit = 0         while j >= 0:             if [j,i] in group:                 hit = 1             elif (hit == 1) & ([j,i] not in group):                 loss = 1             if (hit == 1) & (loss == 1):                 permimeter.append([j,i])                 hit = 0                 loss = 0             j -= 1         i += 1     return permimeter  ## Returns a string that describes the game state def readable(gs):     readthis = ''     readthis += '<<'     for row in gs:         for element in row:             readthis += element     readthis += '>>'     return readthis  ## Counts the territory captured by each player def count():     global gsc     global non_groups     global o_points     global x_points     global boardsize      ## Creates a list of groups (non_groups) of empty positions.     for i in range(0,boardsize):         for j in range(0,boardsize):             if gsc[j][i] == '-':                 new = 1                 for group in non_groups:                     if [i,j] in gperm(group):                         group.append([i,j])                         new = 0                 if new == 1:                     non_groups.append([[i,j]])     concat('-')      o_points = 0     x_points = 0      ## Gives a point to the each player for every pebble they have     ## on the board.     for group in o_groups:         o_points += len(group)     for group in x_groups:         x_points += len(group)      ## The permimeter of these empty positions is here considered,     ## and if every position in the permimeter of a non_group is     ## one player or the other, that player gains a number of points     ## equal to the length of that group (the number of positions     ## that their pieces enclose).     for group in non_groups:         no = 0         for element in gperm(group):             if gsc[element[1]][element[0]] != 'o':                 no = 1         if no == 0:             o_points += len(group)      for group in non_groups:         no = 0         for element in gperm(group):             if gsc[element[1]][element[0]] != 'x':                 no = 1         if no == 0:             x_points += len(group)  ## Checks for capture, and removes the captured pieces from the board def capture(xoro):     global o_groups     global x_groups     global gsf     global restore_o     global restore_x     global edited     if xoro == 'o':         groups = x_groups         otherplayer = 'o'     else:         groups = o_groups         otherplayer = 'x'      ## Checks to see, for each group of a particular player,     ## whether any of the board positions in the     ## perimeter around that group are held by the other player.     ## If any position is not held by the other player,     ## the group is not captured, and is safe.  Otherwise,     ## the group is removed.  But we haven't tested this yet     ## to see if this would return the board to a previous     ## state, so we save the removed groups with the restore lists.     for group in groups:         safe = 0         for element in gperm(group):             if gsf[element[1]][element[0]] != otherplayer:                 safe = 1         if safe != 1:             edited = 1             if xoro == 'o':                 restore_x.append(group)             else:                 restore_o.append(group)             groups.remove(group)      # Sets gsf given the new captures     gsf = initalize()     for group in o_groups:         for point in group:             gsf[point[1]][point[0]] = 'o'     for group in x_groups:         for point in group:             gsf[point[1]][point[0]] = 'x'  ## Checks to see if the new game state, created by the most recent ## move, returns the board to a previous state.  If not, then ## gsc is set as this new state, and gsp is set as what gsc was, and ## the new game state is stored in gscache.  The function returns 1 ## if the move is valid, 0 otherwise. def goodmove():     global gscache     global gsc     global gsp     global gsf     if readable(gsf) not in gscache:         gsp = []         gsc = []         for element in gsf:             gsp.append(element)             gsc.append(element)         gscache += readable(gsf)         return 1     else:         return 0  ## Checks if any groups contain the same point; ## if so, joins them into one group def concat(xoro):     global o_groups     global x_groups     global non_groups     if xoro == 'o':         groups = o_groups     elif xoro == 'x':         groups = x_groups     else:         groups = non_groups     i = 0     ## currentgroups and previousgroups are used to compare the number     ## of groups before this nest of whiles to the number after.  If     ## The number is the same, then nothing needed to be concatinated,     ## and we can move on.  If the number is different, two groups     ## were concatinated, and we need to run through this nest again     ## to see if any other groups need to be joined together.     currentgroups = len(groups)     previousgroups = currentgroups + 1     ## Checks if the positions contained in any group are to be     ## found in any other group.  If so, all elements of the second are     ## added to the first, and the first is deleted.     while previousgroups != currentgroups:         while i < len(groups)-1:             reset = 0             j = i + 1             while j < len(groups):                 k = 0                 while k < len(groups[i]):                     if groups[i][k] in groups[j]:                         for element in groups[j]:                             if element not in groups[i]:                                 groups[i].append(element)                         groups.remove(groups[j])                         reset = 1                     if reset == 1:                         break                     k += 1                 j += 1             if reset == 1:                 i = -1             i += 1         previousgroups = currentgroups         currentgroups = len(groups)  ## Adds point xy to a group if xy is in the ## perimeter of an existing group, or creates ## new group if xy is not a part of any existing group. def addpoint(xy,xoro):     global o_groups     global x_groups     if xoro == 'o':         groups = o_groups     else:         groups = x_groups     new = 1     for group in groups:         if xy in gperm(group):             group.append(xy)             new = 0     if new == 1:         groups.append([xy])  ## Lets the player select a move. def selectmove(xoro):     global boardsize     global gsf     hold = 1     while hold == 1:          minihold = 1         while minihold == 1:             pp = raw_input('Place or pass (l/a)? ')             if pp == 'a':                 return 'pass'             elif pp == 'l':                 minihold = 0                 ## This try...except ensures that the user                 ## raw_inputs only numbers                 error = 0                 try:                     x = int(raw_input('x: '))                 except ValueError:                     error = 1                 try:                     y = int(raw_input('y: '))                 except ValueError:                     error = 1                 if error == 1:                     minihold = 1                     print('invalid')             else:                 print('invalid')         ## Ensures that the raw_input is on the board         if (x > boardsize) | (x < 0) | (y > boardsize) | (y < 0):             print('invalid')         elif gsc[y][x] != '-':             print('invalid')         else:             hold = 0     ## Places the piece on the 'future' board, the board     ## used to test if a move is valid     if xoro == 'o':         gsf[y][x] = 'o'     else:         gsf[y][x] = 'x'      return [x,y]  ## The 'turn,' in which a player makes a move, ## the captures caused by that piece are made, ## the validity of the move is checked, and ## the endgame status is checked. def turn():     global xoro     global notxoro     global player1_pass     global player2_pass     global gameover     hold = 1     while hold == 1:         print()         print('place for '+xoro)         ## By calling selectmove(), the player         ## is given the option of whether to place         ## a piece or to pass, and where to place         ## that piece.         xy = selectmove(xoro)         if xy == 'pass':             if xoro == 'o':                 player1_pass = 1             else:                 player2_pass = 1             hold = 0         ## If the player doesn't pass...         else:             player1_pass = 0             player2_pass = 0             ## The new piece is added to its group,             ## or a new group is created for it.             addpoint(xy,xoro)             ## Groups that have been connected by             ## the this placement are joined together             concat(xoro)             minihold = 1             ## Edited is a value used to check             ## whether any capture is made.  capture()             ## is called as many times as until no pieces             ## are capture (until edited does not change             ## to 1)             edited = 0             while minihold == 1:                 restore_o = []                 restore_x = []                 capture(xoro)                 capture(notxoro)                 if edited == 0:                     minihold = 0                     edited = 0                 else:                     edited = 0             ## Checks to see if the move, given all the             ## captures it causes, would return the board             ## to a previous game state.             if goodmove() == 1:                 hold = 0             ## If the move is invalid, the captured groups need             ## to be returned to the board, so we use             ## the groups stored in the restore lists to             ## restore the o_ and x_groups lists.             else:                 print('invalid move - that returns to board to a previous state')                 for group in restore_o:                     o_groups.append(group)                 for group in restore_x:                     x_groups.append(group)     if (player1_pass == 1) & (player2_pass == 1):         gameover = 1  ## Called to start a game def main():     ## Either 'o' or 'x', determines who's turn it is     global xoro     ## The opposite of xoro, determines who's turn it is not     global notxoro     ## Game State Current, the current layout of the board     ## This value is two-dimensional list, the higher dimension being     ## lists representing the rows and the lower dimension being     ## strings representing individual positions on the board.     ## These strings are either '-', 'o', or 'x'     global gsc     ## 0 or 1, determins whether the current game is ongoing or ended     global gameover     ## Game State Future, same setup as gsc, used for testing the     ## waters of a new move, to see if that move is valid, before     ## gsc is edited to reflect that move     global gsf     ## Two-dimensional lists, the higher dimension being groups, the     ## lower dimension being lists of board positions in a particular     ## group     global o_groups     global x_groups     ## Groups of empty positions     global non_groups     ## String containing all the game states encountered in a particular     ## game, used to check validity of moves     global gscache     ## 0 or 1, for whether the player has passed their turn or not     global player1_pass     global player2_pass     ## Integer value reflecting the score of a player     global o_points     global x_points      ## Creates a blank game state - a blank board     gsc = initalize()     gsf = initalize()     ## Sets initial values     o_groups = []     x_groups = []     non_groups = []     gscache = ''     player1_pass = 0     player2_pass = 0     gameover = 0     o_points = 0     x_points = 0      ## Gives players turns until the end of the game     ## (that is, until both players pass, one after     ## the other)     while gameover != 1:          ## Set it as o-player's turn         xoro = 'o'         notxoro = 'x'         print()         printboard(gsc)          turn()         if gameover == 1:             break          ## Sets it as x-player's turn         xoro = 'x'         notxoro = 'o'         print()         printboard(gsc)          turn()      ## Counts the score of both players     count()     print()     print('final board:')     print()     printboard(gsc)     print()     print('o points: ',str(o_points))     print('x points: ',str(x_points))     ## Determines the winner     if o_points > x_points:         print('o wins')     elif x_points > o_points:         print('x wins')     else:         print('tie')  ## Finally something that is not a function! ## This while loop will start new games for as ## long as the user choses to. while gameon == 1:     main()     hold = 1     while hold == 1:         yn = raw_input('play again (y/n)? ')         if yn == 'n':             gameon = 0             hold = 0         elif yn == 'y':             hold = 0         else:             print('invalid')


Programming Tips For Versatile Coders

[plinker]

Web Design

BoardCreateGamePythonRead

Leave a Reply

Your email address will not be published. Required fields are marked *