Last active
April 6, 2024 09:42
-
-
Save PlusLake/755f1610303b94fd7225c7e4a9bed4ee to your computer and use it in GitHub Desktop.
七並べ(Java Swing)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.swing.*; | |
import java.awt.*; | |
import java.awt.event.*; | |
import java.awt.geom.*; | |
import java.util.*; | |
import java.util.List; | |
import java.util.concurrent.atomic.*; | |
import java.util.function.*; | |
import java.util.stream.*; | |
import static java.awt.RenderingHints.*; | |
public class SevenCard { | |
public static final Dimension SIZE = new Dimension(800, 800); | |
public static void main(String[] args) { | |
frame(panel()); | |
} | |
static void frame(JPanel panel) { | |
JFrame frame = new JFrame("七並べ"); | |
frame.setResizable(false); | |
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | |
frame.setContentPane(panel); | |
frame.pack(); | |
frame.setLocationRelativeTo(null); | |
frame.setVisible(true); | |
} | |
static JPanel panel() { | |
AtomicReference<Game> game = new AtomicReference<>(new Game()); | |
List<Entry<Shape, Runnable>> callbacks = new ArrayList<>(); | |
JPanel panel = new JPanel(null) { | |
public void paintComponent(Graphics graphics) { | |
render((Graphics2D) graphics, game.get(), callbacks); | |
} | |
}; | |
panel.addMouseListener(new MouseAdapter() { | |
public void mousePressed(MouseEvent event) { | |
if (event.getButton() == 3) { | |
game.set(new Game()); | |
panel.repaint(); | |
} | |
for (Entry<Shape, Runnable> callback : callbacks) { | |
if (callback.getKey().contains(event.getX(), event.getY())) { | |
callback.getValue().run(); | |
panel.repaint(); | |
break; | |
} | |
} | |
} | |
}); | |
panel.setPreferredSize(SIZE); | |
return panel; | |
} | |
static void render(Graphics2D graphics, Game game, List<Entry<Shape, Runnable>> callbacks) { | |
callbacks.clear(); | |
graphics.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON); | |
graphics.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON); | |
graphics.setColor(new Color(64, 128, 64)); | |
graphics.fillRect(0, 0, SIZE.width, SIZE.height); | |
graphics.setFont(graphics.getFont().deriveFont(16f)); | |
renderPlayerCard(graphics, game, callbacks); | |
renderBoard(graphics, game); | |
if (game.gameOver) { | |
callbacks.clear(); | |
graphics.setColor(new Color(0, 0, 0, 128)); | |
graphics.fillRect(0, 0, SIZE.width, SIZE.height); | |
graphics.setColor(Color.white); | |
graphics.drawString("Right click to restart", 100, 480); | |
graphics.setFont(graphics.getFont().deriveFont(256f)); | |
graphics.drawString("Game", 40, 200); | |
graphics.drawString("Over", 40, 450); | |
return; | |
} | |
callbacks.forEach(entry -> { | |
graphics.setColor(new Color(255, 128, 128, 64)); | |
graphics.fill(entry.getKey()); | |
}); | |
} | |
static void renderPlayerCard(Graphics2D graphics, Game game, List<Entry<Shape, Runnable>> callbacks) { | |
final int RADIUS = 20; | |
final int INTERVAL = 40; | |
final int WIDTH = 100; | |
final int HEIGHT = 200; | |
List<Card> usableCards = game.usableCards(); | |
for (int j = 0; j < 4; j++) { | |
if (j == game.currentPlayer) { | |
graphics.setColor(new Color(128, 160, 128)); | |
Rectangle pass = new Rectangle(200, 675, 50, 20); | |
graphics.fill(pass); | |
callbacks.add(Map.entry(graphics.getTransform().createTransformedShape(pass), game::pass)); | |
graphics.setColor(new Color(16, 16, 16)); | |
graphics.drawString("PASS", 205, 692); | |
} | |
graphics.setColor(new Color(16, 16, 16)); | |
String message = String.format("Player %d (pass left: %d)", j, game.pass[j]); | |
graphics.drawString(message, 5, 695); | |
for (int i = 0; i < game.cards.get(j).size(); i++) { | |
Card card = game.cards.get(j).get(i); | |
AffineTransform transform = AffineTransform.getTranslateInstance(INTERVAL * i, 700); | |
graphics.transform(transform); | |
graphics.setColor(Color.white); | |
graphics.fillRoundRect(0, 0, WIDTH, HEIGHT, RADIUS, RADIUS); | |
graphics.setColor(Color.black); | |
graphics.drawRoundRect(0, 0, WIDTH, HEIGHT, RADIUS, RADIUS); | |
graphics.setColor(card.suit.color); | |
graphics.drawString(card.toString(), 5, 20); | |
if (j != game.currentPlayer) { | |
graphics.setColor(new Color(0, 0, 0, 128)); | |
graphics.fillRoundRect(0, 0, WIDTH, HEIGHT, RADIUS, RADIUS); | |
} | |
if (j == game.currentPlayer && usableCards.contains(card)) { | |
int width = i + 1 == game.cards.get(j).size() ? WIDTH : INTERVAL; | |
Rectangle rectangle = new Rectangle(0, 0, width, HEIGHT); | |
Shape shape = graphics.getTransform().createTransformedShape(rectangle); | |
callbacks.add(Map.entry(shape, () -> game.useCard(card))); | |
} | |
Try.wrap(() -> graphics.transform(transform.createInverse())).run(); | |
} | |
graphics.rotate(-Math.PI / 2); | |
graphics.translate(-800, 0); | |
} | |
} | |
static void renderBoard(Graphics2D graphics, Game game) { | |
final int RADIUS = 20; | |
final int INTERVAL_X = 4; | |
final int INTERVAL_Y = 4; | |
final int WIDTH = 40; | |
final int HEIGHT = 60; | |
AffineTransform transformOuter = AffineTransform.getTranslateInstance( | |
SIZE.width / 2.0 - WIDTH * 6.5 - INTERVAL_X * 6, | |
SIZE.height / 2.0 - HEIGHT * 2 - INTERVAL_Y * 1.5 | |
); | |
graphics.transform(transformOuter); | |
for (int j = 0; j < 4; j++) { | |
Suit suit = Suit.values()[j]; | |
for (int i = game.board[j][0] - 1; i <= game.board[j][1] - 1; i++) { | |
AffineTransform transformInner = AffineTransform.getTranslateInstance( | |
(WIDTH + INTERVAL_X) * i, | |
(HEIGHT + INTERVAL_Y) * j | |
); | |
graphics.transform(transformInner); | |
graphics.setColor(Color.white); | |
graphics.fillRoundRect(0, 0, WIDTH, HEIGHT, RADIUS, RADIUS); | |
graphics.setColor(Color.black); | |
graphics.drawRoundRect(0, 0, WIDTH, HEIGHT, RADIUS, RADIUS); | |
Card card = new Card(suit, i + 1); | |
graphics.setColor(suit.color); | |
graphics.drawString(card.toString(), 3, 20); | |
Try.wrap(() -> graphics.transform(transformInner.createInverse())).run(); | |
} | |
} | |
Try.wrap(() -> graphics.transform(transformOuter.createInverse())).run(); | |
} | |
static class Game { | |
int currentPlayer = 0; | |
int[][] board = {{ 7, 7 }, { 7, 7 }, { 7, 7 }, { 7, 7 }}; | |
int[] pass = { 3, 3, 3, 3 }; | |
boolean gameOver = false; | |
List<List<Card>> cards; | |
Game() { | |
AtomicInteger counter = new AtomicInteger(); | |
List<Card> deck = new ArrayList<>(deck()); | |
Collections.shuffle(deck); | |
cards = deck | |
.stream() | |
.collect(Collectors.groupingBy(it -> counter.getAndIncrement() / 12)) | |
.values() | |
.stream() | |
.toList(); | |
} | |
List<Card> usableCards() { | |
Predicate<Card> isUsable = card -> | |
board[card.suit.ordinal()][0] - 1 == card.number || | |
board[card.suit.ordinal()][1] + 1 == card.number; | |
return cards | |
.get(currentPlayer) | |
.stream() | |
.filter(isUsable) | |
.toList(); | |
} | |
void useCard(Card card) { | |
if (!usableCards().contains(card)) return; | |
int[] boardLocal = board[card.suit.ordinal()]; | |
if (boardLocal[0] - 1 == card.number) | |
boardLocal[0] = card.number; | |
if (boardLocal[1] + 1 == card.number) | |
boardLocal[1] = card.number; | |
cards.get(currentPlayer).remove(card); | |
if (cards.get(currentPlayer).isEmpty()) | |
gameOver = true; | |
currentPlayer = ++currentPlayer % 4; | |
} | |
void pass() { | |
if (--pass[currentPlayer] < 0) | |
gameOver = true; | |
currentPlayer = ++currentPlayer % 4; | |
} | |
} | |
record Card(Suit suit, int number) { | |
static final String[] CHARACTERS = {"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"}; | |
public String toString() { | |
return suit.character + CHARACTERS[number - 1]; | |
} | |
} | |
static List<Card> deck() { | |
Function<Suit, Stream<Card>> mapper = suit -> IntStream | |
.rangeClosed(1, 13) | |
.filter(i -> i != 7) | |
.mapToObj(i -> new Card(suit, i)); | |
return Stream | |
.of(Suit.values()) | |
.flatMap(mapper) | |
.toList(); | |
} | |
enum Suit { | |
SPADE(Color.black, "♠"), | |
HEART(Color.red, "♡"), | |
CLUB(Color.black, "♧"), | |
DIAMOND(Color.red, "♢"); | |
final Color color; | |
final String character; | |
Suit(Color color, String character) { | |
this.color = color; | |
this.character = character; | |
} | |
} | |
static class Try { | |
static Runnable wrap(RunnableWithException runnable) { | |
return () -> { | |
try { | |
runnable.run(); | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
}; | |
} | |
} | |
interface RunnableWithException { | |
void run() throws Exception; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Kotlin 版も作りました