Four in a row, one click at a time
A two-player Connect Four game written in Java Swing, built on the Model-View-Controller and Observer patterns. This page follows a single click, the winning move on the board here, through every class it touches.
- 6 × 7board
- 4in a row wins
- 69lines checked per move, at most
- 2observer interfaces
-
Click Drop
View
ConnectFourViewis aJFramewith three panels: a turn label at the top, a grid in the middle with a Drop button above each column and aJPanelfor each of the 42 slots, and Exit and Reset at the bottom. Every button reports to the sameactionPerformedmethod.Turn: Player 1
The method compares the event's source with each button. Exit and Reset map straight to the controller; anything else must be a Drop button, so it loops over the seven of them to find the column.
public void actionPerformed(ActionEvent e) { if (e.getSource() == exit) { controller.exit(); } else if (e.getSource() == reset) { controller.reset(); } else { controller.dropChip(getDropButtonPressed(e)); } }Clicking the fifth Drop button calls
controller.dropChip(4). -
The controller relays it
Controller
ConnectFourControllerknows nothing about the rules. It hands the column to the model, then asks the view to update the turn label.public void dropChip(int column) { model.dropChip(column); view.switchTurns(); }The view only sees
ConnectFourControllerInterfaceand the controller only seesConnectFourModelInterface, so each class can be tested with Mockito mocks standing in for the others.Exit and Reset take the same route:
exit()callsmodel.exit(), which ends the program withSystem.exit(0), andreset()callsmodel.reset()and thenview.resetView(). -
The model places the chip
Model
ConnectFourModelstores the board as a 6 × 7intarray:0for an empty slot,1for Player 1 and2for Player 2. A second array,availableSpot, holds the lowest free row in each column, so dropping a chip is a lookup rather than a search.The board as the model sees it
After the move,
board[2][4]holds1andavailableSpot[4]has moved up from2to1.board[availableSpot[column]][column] = currentPlayer.getId(); currentRow = availableSpot[column]; currentColumn = column; availableSpot[column]--; if (availableSpot[column] < 0) { disablingButton = column; }When a column fills up
Every column starts with
availableSpotat5, the bottom row. Each chip moves it up one row. Once it drops below0the column is full, and the model records the column indisablingButtonso the view can switch its Drop button off.Chips dropped into one column and the availableSpot counter after each Chip Lands in row availableSpot after Drop button 1st 5 4 Enabled 2nd 4 3 Enabled 3rd 3 2 Enabled 4th 2 1 Enabled 5th 1 0 Enabled 6th 0 −1 Disabled -
Observers repaint the window
ModelView
The model never calls the view directly. It loops over its registered
ConnectFourObservers and callsupdateGame()on each. The view registered itself in its constructor, so it receives the call and asks the model what changed.The calls so far
- View→Controller
dropChip(4) - Controller→Model
dropChip(4) - Model
board[2][4] = 1,availableSpot[4] = 1 - Model→View
updateGame()throughConnectFourObserver - View→Model
getCurrentRow()→ 2,getCurrentColumn()→ 4,getColor()→ red - View
dropChipInView(2, 4, RED)paints the slot - View→Model
getDisableButton()→ −1, so every Drop button stays on
getColor()returns the current player's color. It is still Player 1's turn here, because the model switches players only after the observers have run. - View→Controller
-
Scan for four in a row
Model
Next,
checkWin()looks across the whole board, not just around the new chip. Each check takes a starting slot that isn't empty and compares it with the next three slots in one direction. On a 6 × 7 board that is at most 69 windows, which is cheap enough not to need anything smarter.Horizontal24 windows Vertical21 windows Left diagonal /12 windows Right diagonal \12 windows For this move, all 24 horizontal and 21 vertical windows come up empty. The 10th left-diagonal window, starting at row 2, column 4, holds four
1s, so the win is found on window 55.checkWin()joins the four scans with||, so it stops at the first line it finds. If nobody has won,boardFull()checks all 42 slots for a0; if none is left, the game is a draw. -
Announce the result and reset
ModelViewController
The model shows a dialog,
Player 1 wins!
orThe game ended as draw!
, and waits for OK. Then it notifies itsWinnerObservers. The view reacts toupdateWinner()by asking the controller to reset, which clears both the model and the window.The rest of the click
- Model
JOptionPane.showMessageDialog(…)showsPlayer 1 wins!
- Model→View
updateWinner()throughWinnerObserver - View→Controller
reset() - Controller→Model
reset()clears the board, refillsavailableSpotwith 5 and makes Player 1 current - Controller→View
resetView()clears every slot, re-enables every Drop button and updates the label - Model
switchPlayer()twice, back indropChip - Controller→View
switchTurns()sets the label toTurn: Player 1
Why Player 1 always starts the next game
Every move ends with a player switch. A finished game adds one more, which cancels it out after the reset.
The current player through the end of a game Moment Current player Reset during updateWinner()Player 1 Extra switch after a win or draw Player 2 Switch at the end of every move Player 1 - Model
How it's put together
Interfaces between the layers
The view only knows the controller's interface, the controller only knows the model's interface, and the model only knows its two observer interfaces. The one concrete link is the ConnectFourView that the controller creates.
Tested layer by layer
More than 100 JUnit 5 tests use Mockito to isolate each class. Win tests intercept the result dialog with MockedStatic, and the Swing tests run on the Event Dispatch Thread.
Checked on every change
GitHub Actions builds every push and pull request, running Checkstyle (Google Java Style) and the full test suite under a virtual display. Tagging a version publishes a release with the JAR.
Run it yourself
You need Java 21 or newer.
Download the game
Built from the latest main branch when this page was published. Run it with:
java -jar connect-four.jar
Build from source
The Gradle wrapper downloads everything else it needs. On Windows, use gradlew.bat.
git clone https://github.com/NiharParikh17/Connect-Four.git
cd Connect-Four
./gradlew run