Morpion solitaire/Java

From Rosetta Code
Works with: Java version 7

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.

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, 16));
        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;
    }
}