J'étudie l'architecture propre et j'essaye diverses conceptions et implémentations. Il y a un article qui semble bon dans le sujet, et j'ai essayé de le repenser et de le réimplémenter avec une conscience d'architecture propre. J'ai commenté la proposition de conception et la proposition de mise en œuvre dans la section des commentaires de l'article original, mais comme elle a été affinée, j'organiserai le processus de conception, etc. Sujet: java j'ai essayé de casser un simple bloc --Qiita
Dans l'architecture propre, les couches sont séparées comme indiqué dans la figure ci-dessous.
L'architecture propre montre également un diagramme de classes.
Je ne pense pas que ce soit si difficile d'extraire la classe du programme de jeu car vous pouvez en faire une classe.
J'ai divisé les classes en gardant à l'esprit les couches.
UI
Devices
JFrame et JPanel combinent un périphérique de souris, un périphérique de clavier et un périphérique d'affichage.
Web
Je pense qu'il est possible de permettre de jouer à des jeux avec un navigateur Web, mais je vais l'omettre.
DB
Il est possible d'enregistrer le meilleur score du jeu, mais de l'omettre.
External Interfaces
Il est possible de se connecter à un utilisateur distant via un réseau et de jouer contre lui, mais l'omettre.
Un groupe d'adaptateurs de conversion de données pour joindre (adapter) des cadres et des pilotes (UI, Web, DB, périphériques, etc.) dans le monde extérieur et des cas d'utilisation dans le monde intérieur.
Gateways
La conversion de sérialisation du relais de données de combat peut être envisagée, mais elle sera omise.
Use Cases
Entities
Un groupe de classes contenant des données telles que des informations de position, des informations de taille et des informations de couleur.
Le diagramme de classe du jeu de rupture de bloc est illustré ci-dessous. J'ai essayé de coder en couleur l'arrière-plan selon le diagramme de couches de l'architecture propre. Pour Entity, j'ai créé une interface de sortie de données pour remplacer le getter. (Interface rouge en jaune) En outre, l'objet rebondi est abstrait en tant que Bounder, et l'objet rebondi (balle) est abstrait en tant que Boundee afin qu'il puisse être traité indépendamment de la classe concrète.
La partie inférieure droite du diagramme de couches de Clean Architecture est illustrée dans la figure ci-dessous.
Lorsqu'il est appliqué au diagramme de configuration de classe de l'architecture propre, il est comme indiqué dans la figure ci-dessous.
La partie Données d'entrée n'est pas due à l'utilisation de types primitifs.
La partie Output Data est un stockage de données de «
L'écran de démarrage, la disposition des blocs et la disposition des balles multiples sont adaptés à l'œuvre originale. Ma politique est de conserver 25 lignes ou moins par méthode afin que vous puissiez le voir sur un écran du terminal.
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.util.function.Consumer;
import java.awt.Component;
import java.awt.CardLayout;
import java.awt.Graphics;
import java.awt.Font;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Breakout extends JFrame {
public static void main(String[] args) {
var breakout = new Breakout();
breakout.playAfterSplash(5);
breakout.setVisible(true);
}
private final BreakoutGame game = new BreakoutGame();
private final CardLayout card = new CardLayout(0, 0);
private Timer timer;
public Breakout() {
setTitle("Rupture de bloc");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
var pane = getContentPane();
pane.setLayout(card);
pane.add(new Splash(), "splash");
pane.add(game.getView(), "game");
pack();
}
public void playAfterSplash(int sec) {
card.show(getContentPane(), "splash");
timer = new javax.swing.Timer(sec * 1000, e -> {
timer.stop();
timer = null;
play();
});
timer.start();
}
public void play() {
card.show(getContentPane(), "game");
game.start();
}
}
class Splash extends JPanel {
@Override
public void paint(Graphics g) {
super.paint(g);
g.setFont(new Font("TimeRoman", Font.CENTER_BASELINE, 30));
g.setColor(Color.red);
g.drawString("Rupture de bloc", 300, 200);
g.drawString("Il démarrera dans 5 secondes!", 250, 300);
g.drawString("Déplacez la souris sur le côté pour faire rebondir la balle", 125, 500);
g.drawString("La pointe de la flèche ci-dessous correspond à la position initiale de la souris", 100, 600);
g.drawString("↓", 425, 700);
}
}
class BreakoutGame {
private final BreakoutViewModel viewModel = new BreakoutViewModel();
private final BreakoutPresenter presenter = new BreakoutPresenter(viewModel);
private final BreakoutUseCase uc = new BreakoutUseCase(presenter);
private final BreakoutView view = new BreakoutView(viewModel, uc.WIDTH, uc.HEIGHT);
private final BreakoutController controller = new BreakoutController(uc, view);
public BreakoutGame() {
presenter.addListener(viewModel -> view.repaint());
presenter.addListener(viewModel -> {
if (viewModel.isGameClear() || viewModel.isGameOver()) {
stop();
}
});
}
public Component getView() {
return view;
}
public void start() {
controller.enable();
}
public void stop() {
controller.disable();
}
}
class BreakoutView extends JPanel {
private final BreakoutViewModel model;
public BreakoutView(BreakoutViewModel model, int width, int height) {
this.model = model;
setPreferredSize(new Dimension(width, height));
setBackground(Color.black);
}
@Override
public void paint(Graphics g) {
super.paint(g);
model.paint(g);
}
}
class BreakoutViewModel {
private BreakoutViewData data;
public void update(BreakoutViewData data) {
this.data = data;
}
public boolean isAvailable() {
return data != null;
}
public boolean isGameClear() {
return data.isGameClear();
}
public boolean isGameOver() {
return data.isGameOver();
}
public void paint(Graphics g) {
if (!isAvailable()) return;
paintBalls(g);
paintWalls(g);
paintBlocks(g);
paintRacket(g);
if (isGameClear()) {
paintGameClear(g);
} else if (isGameOver()) {
paintGameOver(g);
}
}
public void paintWalls(Graphics g) {
final int[] offset = {0, -16, -8};
final int blockWidth = 26, blockHeight = 10, gapX = 6, gapY = 5;
g.setColor(Color.GREEN);
data.viewWalls((wallX, wallY, wallWidth, wallHeight) -> {
for (int y = wallY, iy = 0; y < wallY + wallHeight; y += blockHeight + gapY, iy++) {
for (int blockX = wallX + offset[iy % offset.length]; blockX < wallX + wallWidth; blockX += blockWidth + gapX) {
int x = blockX;
int width = blockWidth;
int height = blockHeight;
if (x < wallX) {
x = wallX;
width -= wallX - blockX;
} else if (x + blockWidth >= wallX + wallWidth) {
width = wallX + wallWidth - x;
}
if (wallY + height >= wallY + wallHeight) {
height = wallY + wallHeight - y;
}
g.fillRect(x, y, width, height);
}
}
});
}
public void paintBlocks(Graphics g) {
data.viewBlocks((x, y, width, height, color) -> {
g.setColor(color);
g.fillRect(x, y, width, height);
});
}
public void paintRacket(Graphics g) {
g.setColor(Color.WHITE);
data.viewRacket((x, y, width, height) -> g.fillRect(x, y, width, height));
}
private void paintBalls(Graphics g) {
g.setColor(Color.RED);
data.viewBalls((x, y, size) -> g.fillOval(x, y, size, size));
}
public void paintGameClear(Graphics g) {
g.setFont(new Font("TimeRoman", Font.BOLD, 50));
g.setColor(Color.orange);
g.drawString("Game Clear!", 300, 550);
}
public void paintGameOver(Graphics g) {
g.setFont(new Font("TimeRoman", Font.BOLD, 50));
g.setColor(Color.orange);
g.drawString("Game Over!", 300, 550);
}
}
class BreakoutPresenter implements BreakoutViewer {
private final BreakoutViewModel viewModel;
private final List<Consumer<BreakoutViewModel>> listeners = new ArrayList<>();
public BreakoutPresenter(BreakoutViewModel viewModel) {
this.viewModel = viewModel;
}
public void addListener(Consumer<BreakoutViewModel> listener) {
listeners.add(listener);
}
public void removeListener(Consumer<BreakoutViewModel> listener) {
listeners.remove(listener);
}
@Override
public void view(BreakoutViewData data) {
viewModel.update(data);
listeners.forEach(listener -> listener.accept(viewModel));
}
}
class BreakoutController {
public static final int MOVE_BALLS_INTERVAL_MILLISEC = 50;
private final BreakoutOperation operation;
private final Component mouseDevice;
private final MouseMotionListener mouseController;
private final Timer ballController;
public BreakoutController(BreakoutOperation operation, Component mouseDevice) {
this.operation = operation;
this.mouseDevice = mouseDevice;
mouseController = makeMouseController();
ballController = makeBallController(MOVE_BALLS_INTERVAL_MILLISEC);
}
private MouseMotionListener makeMouseController() {
return new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent event) {
operation.moveRacket(event.getX());
}
};
}
private Timer makeBallController(int interval_millisec) {
return new javax.swing.Timer(interval_millisec, e -> {
operation.moveBalls();
});
}
public void enable() {
mouseDevice.addMouseMotionListener(mouseController);
ballController.start();
}
public void disable() {
mouseDevice.removeMouseMotionListener(mouseController);
ballController.stop();
}
}
interface BreakoutOperation {
public void moveRacket(int x);
public void moveBalls();
}
interface BreakoutViewer {
public void view(BreakoutViewData data);
}
interface BreakoutViewData {
public boolean isGameClear();
public boolean isGameOver();
public void viewWalls(WallViewer viewer);
public void viewBlocks(BlockViewer viewer);
public void viewRacket(RacketViewer viewer);
public void viewBalls(BallViewer viewer);
}
class BreakoutUseCase implements BreakoutOperation, BreakoutViewData {
public static final int WIDTH = 855;
public static final int HEIGHT = 800;
public static final int WALL_SIZE = 40;
private final BreakoutViewer viewer;
private final Court court;
private final Racket racket;
private final List<Block> blocks;
private final List<Ball> balls;
private final Random rand = new Random();
public BreakoutUseCase(BreakoutViewer viewer) {
this.viewer = viewer;
court = new Court(WIDTH, HEIGHT, WALL_SIZE);
racket = new Racket(WIDTH / 2, HEIGHT - 110, 120, 5, WALL_SIZE, WIDTH - WALL_SIZE);
blocks = makeBlocks();
balls = Arrays.asList(makeBalls());
}
private List<Block> makeBlocks() {
List<Block> blocks = new ArrayList<>();
addBlocks(blocks, 60, 40, 16, 40, 48, 8, 4, Color.YELLOW, 3);
addBlocks(blocks, 300, 40, 16, 40, 16, 8, 3, Color.GREEN, 2);
addBlocks(blocks, 450, 40, 16, 40, 16, 8, 1, Color.GRAY, Block.UNBREAKABLE);
addBlocks(blocks, 600, 40, 16, 40, 16, 9, 4, Color.CYAN, 1);
return blocks;
}
private void addBlocks(List<Block> blocks, int topY,
int width, int height, int gapX, int gapY,
int cols, int rows, Color color, int strength) {
int topX = (WIDTH - width * cols - gapX * (cols - 1)) / 2;
int endY = topY + (height + gapY) * rows;
int endX = topX + (width + gapX) * cols;
for (int y = topY; y < endY; y += height + gapY) {
for (int x = topX; x < endX; x += width + gapX) {
blocks.add(new Block(x, y, width, height, color, strength));
}
}
}
private Ball[] makeBalls() {
return new Ball[] {
makeBall(250, 5, -6, 7),
makeBall(260, -5, -3, 10),
makeBall(420, 4, 6, 8),
makeBall(480, -5, 2, 10),
makeBall(590, 5, -6, 11),
makeBall(550, -5, -3, 12),
makeBall(570, 4, 6, 13),
makeBall(480, -5, 2, 14),
makeBall(490, 5, -6, 8),
makeBall(400, -5, -3, 8),
makeBall(350, 4, 6, 9),
makeBall(400, -5, 2, 10),
makeBall(390, -5, -3, 10),
makeBall(500, 4, 6, 10),
makeBall(530, -5, 2, 7),
};
}
private Ball makeBall(int y, int vx, int vy, int size) {
return new Ball(40 + rand.nextInt(700), y, vx, vy, size);
}
@Override
public void moveRacket(int x) {
racket.move(x);
output();
}
@Override
public void moveBalls() {
balls.forEach(ball -> {
ball.move(HEIGHT);
court.rebound(ball);
blocks.forEach(ball::bound);
ball.bound(racket);
});
output();
}
private void output() {
viewer.view(this);
}
@Override
public boolean isGameClear() {
return blocks.stream().allMatch(block -> block.isCleared());
}
@Override
public boolean isGameOver() {
return balls.stream().allMatch(ball -> ball.isDead());
}
@Override
public void viewWalls(WallViewer viewer) {
court.viewWalls(viewer);
}
@Override
public void viewBlocks(BlockViewer viewer) {
blocks.forEach(block -> block.view(viewer));
}
@Override
public void viewRacket(RacketViewer viewer) {
racket.view(viewer);
}
@Override
public void viewBalls(BallViewer viewer) {
balls.forEach(ball -> ball.view(viewer));
}
}
class Bounder {
protected Rectangle rect;
public Bounder(int x, int y, int width, int height) {
this.rect = new Rectangle(x, y, width, height);
}
public boolean isHit(int x, int y) {
return this.rect.contains(x, y);
}
public void hit() {
// default: nothing to do
}
public void view(BounderViewer viewer) {
viewer.view(rect.x, rect.y, rect.width, rect.height);
}
}
interface BounderViewer {
public void view(int x, int y, int width, int height);
}
class Court {
private final Wall up, left, right;
public Court(int width, int height, int wallSize) {
up = new Wall(0, 0, width, wallSize);
left = new Wall(0, 0, wallSize, height);
right = new Wall(width - wallSize, 0, wallSize, height);
}
public boolean isHit(int x, int y) {
return up.isHit(x, y) || left.isHit(x, y) || right.isHit(x, y);
}
public void rebound(Boundee boundee) {
boundee.bound(up);
boundee.bound(left);
boundee.bound(right);
}
public void viewWalls(WallViewer viewer) {
up.view(viewer);
left.view(viewer);
right.view(viewer);
}
}
class Wall extends Bounder {
public Wall(int x, int y, int width, int height) {
super(x, y, width, height);
}
}
interface WallViewer extends BounderViewer {
}
class Block extends Bounder {
public static final int UNBREAKABLE = -1;
private static final int BROKEN = 0;
private final Color color;
private int strength;
public Block(int x, int y, int width, int height, Color color, int strength) {
super(x, y, width, height);
this.color = color;
this.strength = strength;
}
@Override
public boolean isHit(int x, int y) {
return isBroken() ? false : super.isHit(x, y);
}
@Override
public void hit() {
if (strength > 0) strength--;
}
public boolean isBroken() {
return strength == BROKEN;
}
public boolean isCleared() {
return strength <= 0;
}
public void view(BlockViewer viewer) {
if (isBroken()) return;
viewer.view(rect.x, rect.y, rect.width, rect.height, color);
}
}
interface BlockViewer {
public void view(int x, int y, int width, int height, Color color);
}
class Racket extends Bounder {
public static final int SPEED = 5;
private final int left, right;
public Racket(int centerX, int centerY, int width, int height,
int limitLeft, int limitRight) {
super(centerX - width / 2, centerY - height / 2, width, height);
left = limitLeft;
right = limitRight - width;
}
public void move(int x) {
x -= rect.width / 2;
rect.x = x < left ? left
: x > right ? right
: x;
}
}
interface RacketViewer extends BounderViewer {
}
interface Boundee {
public void bound(Bounder bounder);
}
class Ball implements Boundee {
private int x, y, vx, vy;
private final int size, r;
private boolean alive = true;
public Ball(int x, int y, int vx, int vy, int size) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.size = size | 1; // always odd
this.r = this.size / 2;
}
public void move(int bottomY) {
if (alive) {
x += vx;
y += vy;
alive = y - r < bottomY;
}
}
@Override
public void bound(Bounder bounder) {
boolean up = bounder.isHit(x, y - r);
boolean down = bounder.isHit(x, y + r);
boolean left = bounder.isHit(x - r, y);
boolean right = bounder.isHit(x + r, y);
boolean up_left = bounder.isHit(x - r, y - r);
boolean up_right = bounder.isHit(x + r, y - r);
boolean down_left = bounder.isHit(x - r, y + r);
boolean down_right = bounder.isHit(x + r, y + r);
if (vy < 0 && up && !bounder.isHit(x, y - r - vy) ||
vy > 0 && down && !bounder.isHit(x, y + r - vy)) {
bounder.hit();
vy *= -1;
} else if (vx < 0 && left && !bounder.isHit(x - r - vx, y - r) ||
vx > 0 && right && !bounder.isHit(x + r - vx, y - r)) {
bounder.hit();
vx *= -1;
} else if (up_left && vx < 0 && vy < 0 ||
up_right && vx > 0 && vy < 0 ||
down_left && vx < 0 && vy > 0 ||
down_right && vx > 0 && vy > 0) {
bounder.hit();
vy *= -1;
vx *= -1;
}
}
public boolean isDead() {
return !alive;
}
public void view(BallViewer viewer) {
viewer.view(x - r, y - r, size);
}
}
interface BallViewer {
public void view(int x, int y, int size);
}
J'ai fait de BreakoutViewData
une interface, mais je ne suis pas sûr de pouvoir le faire de cette façon.
Si vous avez des conseils sur la façon de procéder, laissez un commentaire.
Recommended Posts