You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Alexis Drai 71f37b9e18
continuous-integration/drone/push Build is passing Details
📝 Update Readme
2 years ago
Connect4 🚨 ♻️ Clean up IRules 2 years ago
img Correct UML class diagram 2 years ago
.drone.yml ⚙️ Add CI 2 years ago
.gitignore 🎉 2 years ago
README.md 📝 Update Readme 2 years ago

README.md

Build Status

Connect 4

Code coverage and quality

Code quality can be found hosted on SonarCloud

Coverage was maintained above 80%. Some private methods were neglected, but would end up being covered if we implemented this app more completely.

Coverage

Class diagram

Class diagram

Choices

This app consists of a Game class that implements various elements necessary for a game of Connect4. The core of the game is implemented in an IRules protocol, defining the behavior of the game. The protocol specifies a set of variables such as the minimum and maximum number of rows and columns, and the number of chips to align in order to win the game. It also includes methods for determining if the game is over, if a board is valid, and for finding the next player.

The IRules protocol is implemented by the BasicDefaultsNoDiag struct, which provides a basic set of rules for playing Connect4 without diagonal wins. We added defaults in the intializer to specify a minimum of 3 rows and columns, and a maximum of 15 rows and columns, and 4 chips to be aligned in order to win. Moreover, a few unimaginable boards are completely prevented from being legal in this app: the board needs to be large enough, the number of chips to align needs to be within a reasonable interval...

guard (minNbRows >= 3
        && minNbCols >= 3
        && maxNbRows >= minNbRows
        && maxNbCols >= minNbCols
        && nbChipsToAlign >= 2
        && nbChipsToAlign <= minNbCols
        && nbChipsToAlign <= minNbRows) 
else { return nil }

The Game class has two Player objects, a Board struct, and an implementation of the IRules protocol. Game uses the methods and variables defined in the IRules protocol to manage the state and behavior of the game.

Basic Rules

To keep things simple, the basic rules we implemented don't allow for diagonal victories. But because we used an IRules protocol to initialize the Game class, we could implement a more complete set of rules later on.

Player.play(Board, IRules)

We can use the game's board as a parameter in the play() function, and we never have to worry about the Player class destroying the board, because it is a struct, and therefore passed by copy to play(). If someone extends the Bot class and makes a SmarterBot, they could even use said board to make test-plays and decide which play to go for.

Board

Board is implemented as a 2D grid of optional integers. The size of the board can be specified by the number of rows and columns, but it must have at least 3 rows and 3 columns with our current implementation of IRules. Board implements custom string conversion so it can be easily printed to the console. It can insert chips, check if the board is full, and display a victory message.

Game

The scanner property is used to get the input from human players, and the play() method is used to make one play. Itstarts by determining which player is next, based on the grid state and player ids, and then calls the chooseColumn() method on the player to get the selected column. The insertChip() method is called on the board with a selected column. If the chip insertion is successful, the method returns true, otherwise it returns false. There is an isOver property to indicate whether the game is over.

How to play

Here is connect4_cli/main.swift:

var status: (isOver: Bool, result: Result)

if let rules = BasicDefaultsNoDiag(),
   let board = Board(),
   let p1 = Human(withId: 1,
                  withName: "Geraldine Humanman",
                  usingScanner: scan),
   let p2 = Bot(withId: 2,
                withName: "Botty McBotFace"),
   let game = Game(withScanner : scan,
                   withBoard: board,
                   withRules: rules,
                   withPlayer1: p1,
                   withPlayer2: p2) {
    print(game.boardString)
    while(!(game.isOver)) {
        if game.play() {
            print(game.boardString)
            
        }
    }
    print(game.gameOverString)
}

The objects that need to be injected into Game are created first, then injected, before the game loop starts with while(!(game.isOver)). As it is currently, if you simply launched the connect4_cli app, you would start playing against a bot, using the original rules from Connect4 (except: without diagonal victories). You could of course instantiate the objects differently, instead of using the default values of their initializers. Indeed, you could also make p1 and p2 both Humans, of both Bots, as long as you inject two Players into Game.

Bot vs bot

This is what a bot vs bot game looks like. The number on the bot's face is their id.

Bot vs bot