Morpion solitaire/Java
Player vs computer. Click right-mouse button for hints. When multiple lines can be formed, click the green end point of the line you wish to select.
<lang java5>import java.awt.*; import java.awt.event.*; import java.util.*; import java.util.List; import javax.swing.*;
public class MorpionSolitaire extends JFrame {
MorpionSolitairePanel panel;
public static void main(String[] args) { JFrame f = new MorpionSolitaire(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true); }
public MorpionSolitaire() { Container content = getContentPane(); content.setLayout(new BorderLayout()); panel = new MorpionSolitairePanel(); content.add(panel, BorderLayout.CENTER); setTitle("MorpionSolitaire"); pack(); setLocationRelativeTo(null); }
}
class MorpionSolitairePanel extends JPanel {
enum State { START, HUMAN, BOT, OVER }
State gameState = State.START; Grid grid; String message = "Click to start a new game."; int playerScore, botScore; Font scoreFont;
public MorpionSolitairePanel() { setPreferredSize(new Dimension(1000, 750)); setBackground(Color.white);
setFont(new Font("SansSerif", Font.BOLD, 20)); scoreFont = new Font("SansSerif", Font.BOLD, 12);
grid = new Grid(35, 9);
addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { switch (gameState) { case START: gameState = State.HUMAN; message = "Your turn"; playerScore = botScore = 0; grid.newGame(); break; case HUMAN: if (SwingUtilities.isRightMouseButton(e)) grid.showHints(); else { Grid.Result res = grid.playerMove(e.getX(), e.getY()); if (res == Grid.Result.GOOD) { playerScore++;
if (grid.possibleMoves().isEmpty()) gameState = State.OVER; else { gameState = State.BOT; message = "Computer plays..."; } } } break; } repaint(); } });
start(); }
public final void start() { new Thread(new Runnable() { @Override public void run() { Random rand = new Random(); while (true) { try { if (gameState == State.BOT) { Thread.sleep(1500L);
List<Point> moves = grid.possibleMoves(); Point move = moves.get(rand.nextInt(moves.size())); grid.computerMove(move.y, move.x); botScore++;
if (grid.possibleMoves().isEmpty()) { gameState = State.OVER; } else { gameState = State.HUMAN; message = "Your turn"; } repaint(); } Thread.sleep(100L); } catch (InterruptedException ignored) { } } } }).start(); }
@Override public void paintComponent(Graphics gg) { super.paintComponent(gg); Graphics2D g = (Graphics2D) gg; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
grid.draw(g, getWidth(), getHeight());
if (gameState == State.OVER) { message = "No more moves available. "; if (playerScore > botScore) message += "You win. "; else if (botScore > playerScore) message += "Computer wins. "; else message += "It's a tie. "; message += "Click to start a new game."; gameState = State.START; }
g.setColor(Color.white); g.fillRect(0, getHeight() - 50, getWidth(), getHeight() - 50);
g.setColor(Color.lightGray); g.setStroke(new BasicStroke(1)); g.drawLine(0, getHeight() - 50, getWidth(), getHeight() - 50);
g.setColor(Color.darkGray);
g.setFont(getFont()); g.drawString(message, 20, getHeight() - 18);
g.setFont(scoreFont); String s1 = "Player " + String.valueOf(playerScore); g.drawString(s1, getWidth() - 180, getHeight() - 20);
String s2 = "Computer " + String.valueOf(botScore); g.drawString(s2, getWidth() - 100, getHeight() - 20); }
}
class Grid {
enum Result { GOOD, BAD, UGLY }
final int EMPTY = 0, POINT = 1, HORI = 2, VERT = 4, DIUP = 8, DIDO = 16, HORI_END = 32, VERT_END = 64, DIUP_END = 128, DIDO_END = 256, CAND = 512, ORIG = 1024, HINT = 2048;
final int[] basePoints = {120, 72, 72, 975, 513, 513, 975, 72, 72, 120};
int cellSize, pointSize, halfCell, centerX, centerY, origX, origY; int minC, minR, maxC, maxR;
int[][] points; List<Line> lines; Map<Point, Choice> choices; List<Choice> candidates;
class Line { final Point p1, p2;
Line(Point p1, Point p2) { this.p1 = p1; this.p2 = p2; } }
class Choice { int[] dir; List<Point> points;
Choice(List<Point> p, int[] d) { points = p; dir = d; } }
Grid(int cs, int ps) { cellSize = cs; pointSize = ps; halfCell = cs / 2; points = new int[50][50]; minC = minR = 0; maxC = maxR = 50; newGame(); }
final void newGame() { for (int r = minR; r < maxR; r++) for (int c = minC; c < maxC; c++) points[r][c] = EMPTY;
choices = new HashMap<>(); candidates = new ArrayList(); lines = new ArrayList<>(); minC = minR = 18; maxC = maxR = 31;
// cross for (int r = 0; r < 10; r++) for (int c = 0; c < 10; c++) if ((basePoints[r] & (1 << c)) != 0) points[20 + r][20 + c] = POINT; }
void draw(Graphics2D g, int w, int h) { centerX = w / 2; centerY = h / 2; origX = centerX - halfCell - 24 * cellSize; origY = centerY - halfCell - 24 * cellSize;
// grid g.setColor(Color.lightGray);
int x = (centerX - halfCell) % cellSize; int y = (centerY - halfCell) % cellSize;
for (int i = 0; i <= w / cellSize; i++) g.drawLine(x + i * cellSize, 0, x + i * cellSize, h);
for (int i = 0; i <= h / cellSize; i++) g.drawLine(0, y + i * cellSize, w, y + i * cellSize);
// lines g.setStroke(new BasicStroke(2)); for (int i = 0; i < lines.size(); i++) { Line line = lines.get(i); if (i == lines.size() - 1) g.setColor(new Color(0x3399FF)); else g.setColor(Color.orange); int x1 = origX + line.p1.x * cellSize; int y1 = origY + line.p1.y * cellSize; int x2 = origX + line.p2.x * cellSize; int y2 = origY + line.p2.y * cellSize; g.drawLine(x1, y1, x2, y2); }
// points for (int r = minR; r < maxR; r++) for (int c = minC; c < maxC; c++) { int p = points[r][c];
if (p == EMPTY) continue;
if ((p & ORIG) != 0) g.setColor(Color.red);
else if ((p & CAND) != 0) g.setColor(Color.green);
else if ((p & HINT) != 0) { g.setColor(Color.lightGray); points[r][c] &= ~HINT; } else g.setColor(Color.darkGray);
drawPoint(g, c, r); } }
private void drawPoint(Graphics2D g, int x, int y) { x = origX + x * cellSize - (pointSize / 2); y = origY + y * cellSize - (pointSize / 2); g.fillOval(x, y, pointSize, pointSize); }
Result computerMove(int r, int c) { checkLines(r, c); if (candidates.size() > 0) { Choice choice = candidates.get(0); addLine(choice.points, choice.dir); return Result.GOOD; } return Result.BAD; }
Result playerMove(float x, float y) { int r = Math.round((y - origY) / cellSize); int c = Math.round((x - origX) / cellSize);
// see if inside active area if (c < minC || c > maxC || r < minR || r > maxR) return Result.BAD;
// only process when mouse click is close enough to grid point int diffX = (int) Math.abs(x - (origX + c * cellSize)); int diffY = (int) Math.abs(y - (origY + r * cellSize)); if (diffX > cellSize / 5 || diffY > cellSize / 5) return Result.BAD;
// did we have a choice in the previous turn if ((points[r][c] & CAND) != 0) { Choice choice = choices.get(new Point(c, r)); addLine(choice.points, choice.dir); for (Choice ch : choices.values()) { for (Point p : ch.points) points[p.y][p.x] &= ~(CAND | ORIG); } choices.clear(); return Result.GOOD; }
if (points[r][c] != EMPTY || choices.size() > 0) return Result.BAD;
checkLines(r, c);
if (candidates.size() == 1) { Choice choice = candidates.get(0); addLine(choice.points, choice.dir); return Result.GOOD; } else if (candidates.size() > 1) { // we can make more than one line points[r][c] |= ORIG; for (Choice ch : candidates) { List<Point> cand = ch.points; Point p = cand.get(cand.size() - 1); if (p.equals(new Point(c, r))) p = cand.get(0); points[p.y][p.x] |= CAND; choices.put(p, ch); } return Result.UGLY; }
return Result.BAD; }
void checkLine(int dir, int end, int r, int c, int rIncr, int cIncr) { List<Point> result = new ArrayList<>(5); for (int i = -4; i < 1; i++) { result.clear(); for (int j = 0; j < 5; j++) { int y = r + rIncr * (i + j); int x = c + cIncr * (i + j); int p = points[y][x]; if (p != EMPTY && (p & dir) == 0 || (p & end) != 0 || i + j == 0) result.add(new Point(x, y)); else break; } if (result.size() == 5) { candidates.add(new Choice(new ArrayList<>(result), new int[]{dir, end})); } } }
void checkLines(int r, int c) { candidates.clear(); checkLine(HORI, HORI_END, r, c, 0, 1); checkLine(VERT, VERT_END, r, c, 1, 0); checkLine(DIUP, DIUP_END, r, c, -1, 1); checkLine(DIDO, DIDO_END, r, c, 1, 1); }
void addLine(List<Point> line, int[] dir) { Point p1 = line.get(0); Point p2 = line.get(line.size() - 1);
// mark end points for 5T points[p1.y][p1.x] |= dir[1]; points[p2.y][p2.x] |= dir[1];
lines.add(new Line(p1, p2));
for (Point p : line) points[p.y][p.x] |= dir[0];
// growable active area minC = Math.min(p1.x - 1, Math.min(p2.x - 1, minC)); maxC = Math.max(p1.x + 1, Math.max(p2.x + 1, maxC)); minR = Math.min(p1.y - 1, Math.min(p2.y - 1, minR)); maxR = Math.max(p1.y + 1, Math.max(p2.y + 1, maxR)); }
List<Point> possibleMoves() { List<Point> moves = new ArrayList<>(); for (int r = minR; r < maxR; r++) for (int c = minC; c < maxC; c++) { if (points[r][c] == EMPTY) { checkLines(r, c); if (candidates.size() > 0) moves.add(new Point(c, r)); } } return moves; }
void showHints() { for (Point p : possibleMoves()) points[p.y][p.x] |= HINT; }
}</lang>