JP - Hexgame

hexgame

Hexgame is a simple two-player CLI game with one goal: connect two sides of the board! If the red player connects the top and bottom, they win; if the blue player connects the left and right, they win. Any n x n size board (greater than 2) is allowed. Hexgame is fully open-source: the repo can be found here.

how?

In the HexGame struct, there are two main fields that deal with data: game, a Vec<Color> that keeps track of the pieces placed, and logic, a DisjointSet containing every piece as well as symbolic pieces for the edges. As the name suggests, the logic set controls the logic of the game: once the top and bottom or left and right edges are part of the same set, a player has won, and the game is over.

disjoint set

The point of the exercise was to implement a disjoint set data structure, and use it to create a product (originally in Java, see why). The struct itself is surprisingly simple: it contains a field nodes: Vec<isize> in which all values are initialized to -1. Each node represents either the (negative) size of its set if it is the root, or its parent node.

The algorithm to find the root of a set uses path compression: whenever a node is touched, its parent is changed to the root of the set. This causes much shorter traversals on average. The algorithm itself is as follows:

    
  

Let's look at this chunk-by-chunk:

    
  

Negative values represent the size of a set and are only held by roots. So, if the given node is negative, it is already the root of the set and is returned.

    
  

This is the traversal of the set. The first few lines store the current node and the data held by that node, and creates a Vec to keep track of nodes that have been visited. Any positive value represents a parent, so, while data is positive, we can set current to the node's parent and repeat the cycle. Once it reaches a negative value, it has found the root, and the loop exits. Each node touched is also pushed to the to_compress list.

    
  

Each node that was visited is then made a direct child of the parent. This compresses the set and allows for much faster traversal in the future for frequently checked nodes.

The root node stored in current is then returned.

The algorithm to join two sets is as follows:

    
  

Again, let's look at this chunk-by-chunk:

    
  

The provided nodes aren't guaranteed to be roots, so the roots of each node are found and stored.

    
  

If self.find() returns the same root for both nodes, they are already part of the same set, so no joining is necessary.

    
  

The algorithm uses smart-union by size: when the two sets are joined, the root of larger of the two becomes the root of the new set. The size of a set is stored as a negative value, so if root.0 is less than root.1, it becomes the new root (and vice versa). The size of the nodes are then added, and the smaller root's parent is set to the larger root.

game

The HexGame struct is defined as

    
  

Color is an enum containing Blank, Red, or Blue. On initialization, the logic board and game board are created with the provided size squared, plus 4 for the edges. All elements in the game board start with Blank. The top, bottom, left, and right elements are calculated and represented as the last four elements.

To play a piece, either HexGame::play_red() or HexGame::play_blue (nearly identical) is called:

    
  

The function returns true only if the win condition is met. Chunk-by-chunk:

    
  

HexGame::get_neighbors() calculates the immediate neighbors on the hexagonal board given a position. It calls for a bool is_red that simply controls whether the top/bottom or left/right edges are considered.

    
  

If the given position is already taken, no action should be taken, and the function should return false. Otherwise, set the position to Color::Red or Color::Blue, depending on who's turn it is.

    
  

If any of the neighbors are red or edges, they should be joined with the current position.

    
  

The win condition is that the top/bottom edges are connected for red, or the left/right edges are connected for blue. If the win condition is met, the function returns true.


why?

This was originally given as an assignment in the course Algorithms and Data Structures which was taught in Java (barf). We were given a plain text file of moves to parse, the goal being to detect when either player had won. In order to better learn Rust, I've been re-implementing (almost) all of assignments in it. For this one, parsing a static file and detecting the win condition seemed boring, so I made it interactive as an exercise.