The Nim game is one of the most favored games in game theory, because its winning strategy is easy to figure out. There are several different variations of the game and it can be played as a single-player game (PC vs. player) or as a two-player game (two players communicate via bluetooth or TCP/IP - check out bluetooth or TCP/IP games).
The simplest variation starts with 15 matches in one row. The player removes 1, 2 or 3 matches by clicking on them. Afterwards the computer removes 1 to 3 matches. And so on. The one removing the last match loses.
Program code:
// NimEx1.java import ch.aplu.jgamegrid.*; import java.awt.Color; import java.awt.Font; import java.awt.Point; import java.util.ArrayList; public class NimEx1 extends GameGrid implements GGMouseListener, GGButtonListener { private int nbMatches; private int nbTakenMatches; private GGBackground bg; private GGButton okBtn = new GGButton("sprites/ok.gif", true); private GGButton newBtn = new GGButton("sprites/new.gif", true); public NimEx1() { super(56, 9, 12, false); bg = getBg(); addMouseListener(this, GGMouse.lClick); addActor(okBtn, new Location(50, 4)); okBtn.addButtonListener(this); addActor(newBtn, new Location(50, 4)); newBtn.addButtonListener(this); init(); show(); } public void init() { nbMatches = 15; nbTakenMatches = 0; bg.clear(); for (int i = 0; i < 15; i++) addActor(new Match(), new Location(2 + 3 * i, 4)); okBtn.show(); newBtn.hide(); refresh(); setTitle(nbMatches + " matches. Click on 1, 2 or 3 matches to remove, then click 'OK'."); } public boolean mouseEvent(GGMouse mouse) { Location loc = toLocationInGrid(mouse.getX(), mouse.getY()); Actor actor = null; for (int y = 2; y < 7; y++) { actor = getOneActorAt(new Location(loc.x, y), Match.class); if (actor != null) break; } if (actor != null) { if (nbTakenMatches == 3) setTitle("Take a maximum of 3. Click 'OK' to continue!"); else { actor.removeSelf(); nbMatches--; setTitle(nbMatches + " matches remaining. Click 'OK' to continue."); nbTakenMatches++; if (nbMatches == 0) { setTitle("Press 'new Game' to play again."); bg.setPaintColor(Color.red); bg.setFont(new Font("Arial", Font.BOLD, 90)); bg.drawText("You lost!", new Point(toPoint(new Location(3, 6)))); okBtn.hide(); newBtn.show(); } refresh(); } } return false; } public void computerMove() { int nbRemovedMatches = nbMatches - nbWin(nbMatches); if (nbRemovedMatches == 0) nbRemovedMatches = 1; // optimal strategy impossible nbMatches = nbMatches - nbRemovedMatches; for (int x = 0; x < nbRemovedMatches; x++) { ArrayList<Actor> matches = getActors(Match.class); int k = (int)(Math.random() * matches.size()); removeActor(matches.get(k)); // take random match } setTitle(nbMatches + " matches remaining. Your move now."); if (nbMatches == 0) { bg.setPaintColor(Color.red); bg.setFont(new Font("Arial", Font.BOLD, 96)); bg.drawText("You win!", new Point(toPoint(new Location(3, 6)))); okBtn.hide(); newBtn.show(); setTitle("You win, press 'New Game' to play again."); } refresh(); } // returns next lower winning number private int nbWin(int n) { return ((n - 1) / 4) * 4 + 1; } public void buttonClicked(GGButton button) { if (nbMatches == 0) init(); else { if (nbTakenMatches == 0) setTitle("You have to remove at least 1 match!"); else { nbTakenMatches = 0; computerMove(); } } } public void buttonPressed(GGButton button) { } public void buttonReleased(GGButton button) { } public static void main(String[] args) { new NimEx1(); } } // -------------class Match-------------------------- class Match extends Actor { public Match() { super("sprites/match.gif"); } } |
Explaining the program code:
init() | the method init() initializes a new game (matches and the OK-button are shown). It is called at the start and after each restart. Since the matches are long and thin, the cell size of the grid is small and a match is placed over more than one cell vertically |
setTitle() | the output is shown in the title bar of the game window |
for (int y = 2; y < 7; y++) { actor = getOneActorAt(new Location(loc.x, y), Match.class); } |
after the mouse click, several cells of the same x-coordinates need to be checked |
private int nbWin(int n) { return ((n - 1) / 4) * 4 + 1; } |
calculates the remaining number of matches. The computer then adjusts its winning strategy and removes the ideal number of matches. |
implements GGButtonListener |
implements a ButtonListener with the callbackmethods buttonClicked() buttonPressed() and buttonReleased() |
GGButton okBtn = new GGButton("sprites/ok.gif", true); okBtn.addButtonListener(this); |
initializes an OK-button and registers a GGButtonListener. The parameter true allows three different views of the button (default, over, click). The Sprite pictures ok_0.gif, ok_1.git, ok_2.gif need to be placed inside the directory sprites |
okBtn.hide() newBtn.show() |
both buttons okBtn and newBtn have the same dimensions and the same position. At the start of the game, the okBtn-button is shown and is used to end a turn. If the game is over (nbMaches = 0), the okBtn-button is hidden and the newBtn-button is shown. Clicking this button restarts the game |
Rules: This time, it's the player that removes the last match that wins. Can you figure out the winnig strategy in this variation?
|
![]() |