This commit is contained in:
KeyZiro 2021-12-06 22:07:53 +01:00
commit 5c0578eacd
17 changed files with 893 additions and 0 deletions

140
Block.java Normal file
View File

@ -0,0 +1,140 @@
/**
* La classe <code>Block</code> est utilisée pour créer les blocs
*
* @version 0.1
* @author Adil HAMMERSCHMIDT & Lucas GRANDJEAN
*/
import java.awt.*;
import javax.swing.*;
public class Block extends JPanel {
private int column;
private int line;
private char color;
private int status;
/**
* Constructeur uniquement destiné à la création des variables publiques.
*
* @param blockColumn le numéro de la colonne se trouve le bloc
* @param blockLine le numéro de la ligne se trouve le bloc
* @param blockColor la couleur du bloc
*/
public Block(int blockColumn, int blockLine, char blockColor) {
super();
this.column = blockColumn;
this.line = blockLine;
this.color = blockColor;
this.status = 1;
}
/**
* Renvoie le numéro de colonne du bloc
*
* @return Renvoie le numéro de colonne du bloc (de 0 à 14)
*/
public int getColumn() {
return this.column;
}
/**
* Renvoie le numéro de ligne du bloc
*
* @return Renvoie le numéro de ligne du bloc (de 0 à 9)
*/
public int getLine() {
return this.line;
}
/**
* Renvoie la couleur du bloc
*
* @return Renvoie la couleur du bloc ('R', 'V' ou 'B')
*/
public char getColor() {
return this.color;
}
/**
* Renvoie le status
*
* @return Renvoie le status du bloc (1 ou 0)
*/
public int getStatus() {
return this.status;
}
/**
* Modifie le numéro de colonne du bloc
*
* @param blockColumn le numéro de la colonne que l'on souhaite affecter au bloc (de 0 à 14)
*/
public void setColumn(int blockColumn) {
this.column = blockColumn;
}
/**
* Modifie le numéro de ligne du bloc
*
* @param blockLine le numéro de la ligne que l'on souhaite affecter au bloc (de 0 à 9)
*/
public void setLine(int blockLine) {
this.line = blockLine;
}
/**
* Modifie la couleur du bloc
*
* @param blockColor la couleur que l'on souhaite affecter au bloc ('R', 'V' ou 'B')
*/
public void setColor(char blockColor) {
this.color = blockColor;
}
/**
* Modifie le status du bloc
*
* @param blockStatus le status que l'on souhaite affecter au bloc (1 ou 0)
*/
public void setStatus(int blockStatus) {
this.status = blockStatus;
}
/**
* Actualise les graphiques du block
*/
@Override
public void paintComponent(Graphics g) {
if (this.isOpaque()) {
g.setColor(this.getBackground());
g.fillRect(0, 0, this.getWidth(), this.getHeight());
}
if(this.status == 1) {
if(this.color == 'R') {
Image img = getToolkit().getImage(this.getClass().getResource("/img/rouge.png"));
g.drawImage(img, 0, 0, this);
}
else if(this.color=='V') {
Image img = getToolkit().getImage(this.getClass().getResource("/img/vert.png"));
g.drawImage(img, 0, 0, this);
}
else if(this.color=='B') {
Image img = getToolkit().getImage(this.getClass().getResource("/img/bleu.png"));
g.drawImage(img, 0, 0, this);
}
}
else {
g.setColor(Color.WHITE);
g.drawOval(0, 0, 50, 50);
g.fillOval(0, 0, 50, 50);
}
}
}

44
FinalScreen.java Normal file
View File

@ -0,0 +1,44 @@
/**
* La classe <code>FinalScreen</code> est utilisée pour afficher l'écran de fin de partie
*
* @version 0.1
* @author Adil HAMMERSCHMIDT & Lucas GRANDJEAN
*/
import javax.swing.*;
import java.awt.*;
public class FinalScreen extends JFrame {
/**
* Constructeur déstiné à créer l'écran de fin.
*
* @param gameInstance l'instance de jeu
*/
public FinalScreen(int score) {
FinalScreenListener fsListener = new FinalScreenListener(this);
JPanel scorePanel = new JPanel();
JLabel scoreLabel = new JLabel("Votre score : " + String.valueOf(score));
scorePanel.add(scoreLabel, JPanel.CENTER_ALIGNMENT);
JPanel p = new JPanel();
p.setLayout(new GridLayout(1,2));
JButton restartBtn = new JButton("Revenir");
JButton exitBtn = new JButton("Quitter");
restartBtn.addActionListener(fsListener);
exitBtn.addActionListener(fsListener);
scorePanel.add(scoreLabel);
p.add(restartBtn);
p.add(exitBtn);
this.add(scorePanel, BorderLayout.NORTH);
this.add(p);
this.setPreferredSize(new Dimension(300, 300));
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}

33
FinalScreenListener.java Normal file
View File

@ -0,0 +1,33 @@
/**
* La classe <code>FinalScreenListener</code> est utilisée pour donner un rôle aux boutons de l'écran de fin
*
* @version 0.1
* @author Adil HAMMERSCHMIDT & Lucas GRANDJEAN
*/
import java.awt.event.*;
import javax.swing.JFrame;
public class FinalScreenListener implements ActionListener {
private JFrame fsFrame;
public FinalScreenListener(JFrame fsFrame) {
this.fsFrame = fsFrame;
}
public void actionPerformed(ActionEvent e) {
// On récupère l'action afin d'exécuter le code qui lui est attribué
String action = e.getActionCommand();
switch(action)
{
case "Revenir":
fsFrame.dispose();
break;
case "Quitter":
System.exit(0);
break;
}
}
}

395
Grid.java Normal file
View File

@ -0,0 +1,395 @@
/**
* La classe <code>Grid</code> est utilisée pour générer la grille de blocs
*
* @version 0.1
* @author Adil HAMMERSCHMIDT & Lucas GRANDJEAN
*/
import java.io.*;
import java.awt.*;
import javax.swing.*;
import java.util.Random;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
public class Grid extends JPanel {
private Random random = new Random();
private int col = 15;
private int row = 10;
private char charTab[][] = new char[10][15];
private Block blockTab[][] = new Block[10][15];
private String[] connectedCellList = null;
private final ArrayList<String> cellNeigbors = new ArrayList<>();
private HUD gameHUD;
private int score = 0;
/**
*Constructeur de la Grid : Génère une grille aléatoire.
*@param HUD Nom de l'HUD
*/
public Grid(HUD gameHUD) {
this.gameHUD = gameHUD;
this.setLayout(new GridLayout(row,col,0,0));
this.setBackground(Color.WHITE);
this.initGrid();
}
/**
*TODO : Surchage du constructeur avec le fichier
*
*@param s Nom du fichier
*@param HUD Nom de l'HUD
*/
public Grid(String s, HUD gameHUD) {
this.gameHUD = gameHUD;
this.setLayout(new GridLayout(row,col,0,0));
try {
File grid_file = new File(s);
FileInputStream flux = new FileInputStream(grid_file);
this.initGrid(flux);
try {
flux.close();
}
catch (IOException e) {
System.err.println("Impossible de fermer le fichier " + s + " !");
}
}
catch (FileNotFoundException e) {
System.err.println("Impossible d'ouvrir le fichier " + s + " !");
}
}
/**
* Met en place la grille
*
*/
public void initGrid() {
GridMouseHandler gridMouseHandler = new GridMouseHandler();
int i,j;
for (i=0; i<row; i++) {
for (j=0; j<col; j++) {
int randomBlock=this.random.nextInt(3);
try {
switch(randomBlock) {
case 0:
charTab[i][j]='R';
break;
case 1:
charTab[i][j]='V';
break;
case 2:
charTab[i][j]='B';
break;
default:
throw new OutOfRangeBlock();
}
this.blockTab[i][j] = new Block(j, i, charTab[i][j]);
this.blockTab[i][j].addMouseListener(gridMouseHandler);
this.blockTab[i][j].setBackground(Color.WHITE);
this.add(this.blockTab[i][j]);
} catch(OutOfRangeBlock e) {
System.out.println(e);
}
}
}
}
/**
* Surchage de la fonction initGrid pour fonctionner avec les fichiers
*
*@param f Flux du fichier
*/
public void initGrid(FileInputStream f) {
GridMouseHandler gridMouseHandler = new GridMouseHandler();
int i,j;
char c;
try {
for (i=0; i<row; i++) {
for (j=0; j<col; j++) {
c = (char)f.read();
if (c == '\r') {
c = (char)f.read();
c = (char)f.read();
}
charTab[i][j]=c;
this.blockTab[i][j] = new Block(j, i, charTab[i][j]);
this.blockTab[i][j].addMouseListener(gridMouseHandler);
this.blockTab[i][j].setBackground(Color.WHITE);
this.add(this.blockTab[i][j]);
}
}
}
catch(IOException e) {
System.err.println("Impossible de lire le fichier !");
}
}
/**
* Cette méthode explore avec récursivité une matrice, partant
* de la ligne et colonne spécifiée en argument, processe ses valeurs
* puis regarde s'il possède un voisin horizontalement, verticalement et diagonalement (selon les options)
* qui possède les mêmes valeurs. Chaques celulles voisines détectées rappellent la fonction,
* jusqu'à ce qu'il n'y ai plus de voisins disponibles.
* Toutes les celulles voisines ont les valeurs des lignes/colonnes
* storés dans une Array String (délimité par un ;).
*
* @param matrix (2D Array Int) La matrice pour check les voisins
*
* @param matrix_X (int) Index de la ligne de base
*
* @param matrix_Y (int) Index de la colonne de base
*
* @return (String[] Array) Une Array de String contenant des sets délimités par un ";"
* avec les valeurs des index de la Ligne;Colonne pour les cellules voisines détectées.
* (Cellule de base inclue au début)
*/
public String[] getMatrixNeighCells(char[][] matrix, int matrix_X, int matrix_Y) {
/* Dans une matrice on peut avoir jusqu'à 8 voisins qui contiennent la même valeur.
On va exclure les diagonales. */
int[][] directions = {
{0, -1}, // Haut
{-1, 0}, // Gauche
{1, 0}, // Droite
{0, 1}, // Bas
};
// Cellule de base
String baseCell = String.valueOf((matrix_X)) + ";" + String.valueOf((matrix_Y));
if (!this.cellNeigbors.contains(baseCell)) {
this.cellNeigbors.add(baseCell);
}
for (int[] d : directions) {
int dx = d[0]; // Ligne
int dy = d[1]; // Colonne
if ((matrix_X + dx) >= 0 && (matrix_X + dx) < (matrix.length)
&& (matrix_Y + dy) >= 0 && (matrix_Y + dy) < (matrix[0].length)
&& matrix[matrix_X + dx][matrix_Y + dy] == matrix[matrix_X][matrix_Y]
&& matrix[matrix_X][matrix_Y] != '1') {
// Pour les celulles avec des voisins à la même valeur
String neighbor = String.valueOf((matrix_X + dx)) + ";" + String.valueOf((matrix_Y + dy));
if (!this.cellNeigbors.contains(neighbor)) {
this.cellNeigbors.add(neighbor);
// Récursivité pour les voisins
String[] tmp = getMatrixNeighCells(matrix, matrix_X + dx, matrix_Y + dy);
if (tmp != null) {
for (String str : tmp) {
if (!this.cellNeigbors.contains(str)) {
this.cellNeigbors.add(str);
}
}
}
}
}
}
// On return la liste s'il y'a au moins une cellule voisine aux valeurs adjacente à celle de base
if (this.cellNeigbors.size() >= 2) {
return this.cellNeigbors.toArray(new String[this.cellNeigbors.size()]);
}
return null;
}
/**
* Permet de vérifier s'il y'a des lignes vides en dessous de chaques boules.
* S'il y'en a, on abaisse d'un niveau la boule et on fait une récursion.
*@param j Numéro de colonne
*/
public void reorganizeCol(int j) {
for(int i = 0; i < row-1; i++) {
if(blockTab[i][j].getStatus() == 1 && blockTab[i+1][j].getStatus() == 0) {
blockTab[i+1][j].setColor(blockTab[i][j].getColor());
blockTab[i+1][j].setStatus(1);
blockTab[i][j].setStatus(0);
blockTab[i+1][j].setBackground(Color.WHITE);
blockTab[i][j].setBackground(Color.WHITE);
charTab[i][j]='1';
charTab[i+1][j]=blockTab[i+1][j].getColor();
reorganizeCol(j);
}
}
this.validate();
this.repaint();
}
/**
* Permet de vérifier si une des colonnes est vide. Si oui, on décale et on relance.
*@param i Numéro de col
*/
public void reorganizeRow(int i) {
int nbCount = 0;
for(int j = 0; j < row; j++) {
if(blockTab[j][i].getStatus() == 0) {
nbCount++;
}
if(nbCount == row) {
for(int k = 0; k < row; k++) {
blockTab[k][i].setColor(blockTab[k][i+1].getColor());
if(blockTab[k][i+1].getStatus() == 1) {
blockTab[k][i].setStatus(1);
blockTab[k][i+1].setStatus(0);
charTab[k][i]=blockTab[k][i+1].getColor();
}
blockTab[k][i].setBackground(Color.WHITE);
blockTab[k][i+1].setBackground(Color.WHITE);
charTab[k][i+1]='1';
}
if(i != col-2) {
reorganizeRow(i+1);
}
}
}
this.validate();
this.repaint();
}
/**
* Getter du tableau de la grille
*
*@return tableau de la grille
*/
public char[][] getGrid() {
return this.charTab;
}
/**
* Un setter avec incrémentation du score
* On change le label du score.
*/
public void addScore(int score) {
this.score += score;
gameHUD.setScoreLabel(this.score);
}
/**
* Getter du score
* @return le score
*/
public int getScore() {
return this.score;
}
/**
* On itère chaque blocs pour savoir s'il leur reste des voisins
* Si non, on fait gagner le joueur
* @return un bool, true marquant la victoire du joueur.
*/
public void checkWin() {
int moveLeft = 0;
for (int i=0; i< blockTab.length; i++) {
for (int j=0; j< blockTab[i].length; j++) {
cellNeigbors.clear();
connectedCellList = getMatrixNeighCells(getGrid(), i,j);
if (connectedCellList != null && connectedCellList.length > 1) moveLeft++;
}
}
if(moveLeft <= 0) endGame();
}
/**
* Créé l'écran de fin de partie
*/
public void endGame() {
FinalScreen fs = new FinalScreen(score);
JFrame topFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
topFrame.dispose();
}
/**
* Mouse Event
*/
public class GridMouseHandler extends MouseAdapter {
/**
* Si le joueur passe la souris sur un des Blocks,
* on vérifie les cellules voisines du block ponté
* puis on change le backGround des cellules voisines
*/
@Override
public void mouseEntered(MouseEvent evt) {
Block source = (Block) evt.getSource();
for (int i = 0; i < blockTab.length; i++) {
for (int j = 0; j < blockTab[i].length; j++) {
if (blockTab[i][j] == source) {
cellNeigbors.clear();
connectedCellList = getMatrixNeighCells(getGrid(), i, j);
if (connectedCellList != null && connectedCellList.length > 1) {
for (String cells : connectedCellList) {
int x = Integer.valueOf(cells.split(";")[0]);
int y = Integer.valueOf(cells.split(";")[1]);
blockTab[x][y].setBackground(Color.YELLOW);
}
}
}
}
}
}
/**
* Si le joueur quitte le block avec le pointeur de souris,
* on remet le background à sa couleur de base
*/
@Override
public void mouseExited(MouseEvent evt) {
if (connectedCellList != null) {
for (String cells : connectedCellList) {
int x = Integer.valueOf(cells.split(";")[0]);
int y = Integer.valueOf(cells.split(";")[1]);
blockTab[x][y].setBackground(Color.WHITE);
}
}
}
/**
* Si le joueur clique et relâche la souris,
* on désactive le block (en changeant le status),
* on remet le fond, on lui assigne une couleur hors du champs
* (afin de désactiver le surlignage), et on réorganise les
* lignes et colonnes du grid.
* On cherche aussi si le joueur a gagné en appelant CheckWin()
*/
@Override
public void mouseReleased(MouseEvent evt) {
if (connectedCellList != null) {
int ccListSize = connectedCellList.length;
for (String cells : connectedCellList) {
int x = Integer.valueOf(cells.split(";")[0]);
int y = Integer.valueOf(cells.split(";")[1]);
charTab[x][y]='1';
blockTab[x][y].setStatus(0);
blockTab[x][y].setBackground(Color.WHITE);
}
addScore((ccListSize-2) * (ccListSize-2));
for(int j = 0; j < col; j++) {
reorganizeCol(j);
}
for(int i = (col-2) ; i >= 0; i--) {
reorganizeRow(i);
}
}
checkWin();
mouseEntered(evt);
}
}
}

35
HUD.java Normal file
View File

@ -0,0 +1,35 @@
/**
* La classe <code>HUD</code> est utilisée pour générer le HUD (interface) du jeu.
*
* @version 0.1
* @author Adil HAMMERSCHMIDT & Lucas GRANDJEAN
*/
import java.awt.Color;
import javax.swing.*;
import javax.swing.border.MatteBorder;
public class HUD extends JPanel {
private int score = 0;
public JLabel label2;
/**
*Constructeur du HUD : Genère un HUD.
*/
public HUD() {
this.setBorder(new MatteBorder(1, 1, 2, 1, Color.black));
JLabel label = new JLabel("Score : ");
label2 = new JLabel(Integer.toString(score));
this.add(label);
this.add(label2);
}
/**
* Setter du Score.
*/
public void setScoreLabel(int score) {
label2.setText(Integer.toString(score));
}
}

5
Main.java Normal file
View File

@ -0,0 +1,5 @@
public class Main {
static public void main(String[] args) {
Menu m = new Menu();
}
}

25
Makefile Normal file
View File

@ -0,0 +1,25 @@
JC = javac
.SUFFIXES: .java .class
.java.class:
$(JC) $*.java
CLASSES = \
Menu.java \
MenuListener.java \
SameGame.java \
HUD.java \
FinalScreen.java \
FinalScreenListener.java \
Grid.java \
Main.java
default: classes
classes: $(CLASSES:.java=.class)
run:
make
java Main
clean:
$(RM) *.class

73
Menu.java Normal file
View File

@ -0,0 +1,73 @@
/**
* La classe <code>Menu</code> est utilisée pour créer le menu du jeu
*
* @version 0.1
* @author Adil HAMMERSCHMIDT & Lucas GRANDJEAN
*/
import java.awt.*;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JFrame;
import javax.swing.border.BevelBorder;
public class Menu extends JFrame {
private JButton playBtn;
private JButton playFromFileBtn;
private JButton quitBtn;
public Menu() {
JFrame frame = new JFrame("SameGame by Lucas Grandjean & Adil Hammerschmidt");
frame.setPreferredSize(new Dimension(300, 300));
frame.add(new MenuPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public class MenuPanel extends JPanel {
public MenuPanel() {
MenuListener mListener = new MenuListener();
setOpaque(true);
setBackground(new Color(67,133,200));
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.anchor = GridBagConstraints.NORTH;
add(new JLabel("<html><h1><strong><i>SameGame</i></strong></h1><hr></html>"), gbc);
add(new JLabel("<html>Lucas GRANDJEAN</html>"), gbc);
add(new JLabel("<html>Adil HAMMERSCHMIDT<hr></html>"), gbc);
gbc.anchor = GridBagConstraints.CENTER;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(5, 0, 5, 0);
JPanel buttons = new JPanel(new GridBagLayout());
buttons.setBackground(new Color(57,108,160));
buttons.setBorder(new BevelBorder(BevelBorder.RAISED));
playBtn = new JButton("Jouer");
playBtn.addActionListener(mListener);
playFromFileBtn = new JButton("Charger une grille");
playFromFileBtn.addActionListener(mListener);
quitBtn = new JButton("Quitter");
quitBtn.addActionListener(mListener);
buttons.add(playBtn, gbc);
buttons.add(playFromFileBtn, gbc);
buttons.add(quitBtn, gbc);
gbc.weighty = 1;
add(buttons, gbc);
}
}
}

39
MenuListener.java Normal file
View File

@ -0,0 +1,39 @@
/**
* La classe <code>MenuListener</code> est utilisée pour donner un rôle aux boutons du menu
*
* @version 0.1
* @author Adil HAMMERSCHMIDT & Lucas GRANDJEAN
*/
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.io.*;
import javax.swing.JFileChooser;
public class MenuListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
// On récupère l'action afin d'exécuter le code qui lui est attribué
String action = e.getActionCommand();
switch(action)
{
case "Jouer":
SameGame g1 = new SameGame();
break;
case "Charger une grille":
// On sélectionne le fichier à l'aide de JFileChooser
JFileChooser chooser = new JFileChooser();
int ret = chooser.showOpenDialog(null);
if (ret == JFileChooser.APPROVE_OPTION) {
SameGame g2 = new SameGame(chooser.getSelectedFile().getAbsolutePath());
}
break;
case "Quitter":
System.exit(0);
break;
}
}
}

8
OutOfRangeBlock.java Normal file
View File

@ -0,0 +1,8 @@
public class OutOfRangeBlock extends Exception {
@Override
public String toString() {
return ("Le bloc est en dehors de la range demandée");
}
}

39
README.md Normal file
View File

@ -0,0 +1,39 @@
# SameGame
Ce jeu est une copie du célèbre "SameGame"
## Projet Tutoré 2021
Ce jeu a été conçu dans le but du projet tutoré de 2021, à l'IUT Sénart-Fontainebleau.
### Installation
Grâce au Makefile intégré, l'installation sera très simple.
```
Clonez le projet dans un dossier vide
```
```
Utilisez la commande make afin de compiler le jeu
```
```
Profitez d'une expérience de jeu inégalée avec make run !
```
### Langage de programmation
* [Java](https://en.wikipedia.org/wiki/Java_(programming_language)) - Le langage Java.
## Auteurs
* **Lucas Grandjean**
* **Adil Hammerschmidt**
## Licence
Ce jeu est libre de droit, utilisez le comme bon vous semble !
## Remerciements
* Les élèves du DUT Informatique pour leur aide
* Les professeurs du DUT Informatique pour leur conseils

47
SameGame.java Normal file
View File

@ -0,0 +1,47 @@
/**
* La classe <code>SameGame</code> est utilisée pour assembler HUD et Grid afin de former le jeu.
*
* @version 0.1
* @author Adil HAMMERSCHMIDT & Lucas GRANDJEAN
*/
import java.awt.BorderLayout;
import javax.swing.*;
public class SameGame extends JFrame {
private int tempScore = 0;
private HUD gameHUD = new HUD();
/**
* Constructeur de classe
*/
public SameGame() {
Grid gameGrid = new Grid(this.gameHUD);
this.add(BorderLayout.CENTER, gameGrid);
this.add(BorderLayout.NORTH, this.gameHUD);
this.pack();
this.setSize(800, 650);
this.setLocation(300, 300);
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
/**
* (Surchage) Constructeur de classe
* @param s Fichier texte de la Grid
*/
public SameGame(String s) {
Grid gameGrid = new Grid(s, this.gameHUD);
this.add(BorderLayout.CENTER, gameGrid);
this.add(BorderLayout.NORTH, this.gameHUD);
this.pack();
this.setSize(800, 650);
this.setLocation(300, 300);
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
}

BIN
img/bleu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
img/rouge.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
img/vert.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
rapport_pt_final.pdf Normal file

Binary file not shown.

10
test.txt Normal file
View File

@ -0,0 +1,10 @@
RVVRRVRVBBBBRBV
BVVVVRBVVBRRVRB
VBBRVRBVRRBRRRR
BRBVBRBBVVBRVRV
RVBVBBBRRBRRRBV
RVVVBBRBVVBVVRB
BRBRBBBRBVRVRRV
VRRVVBBVVBBRVVV
BVRRVVBRVRRRBVV
BBRBBBBRVVRRVRB