|
package codesquad; |
|
|
|
import javax.swing.*; |
|
import javax.swing.Timer; |
|
import java.awt.*; |
|
import java.awt.event.ActionListener; |
|
import java.beans.PropertyChangeEvent; |
|
import java.beans.PropertyChangeListener; |
|
import java.beans.PropertyChangeSupport; |
|
import java.util.*; |
|
import java.util.List; |
|
|
|
public class TestImpl { |
|
public static void main(String[] args) { |
|
new Controller(); |
|
} |
|
} |
|
|
|
class Controller implements PropertyChangeListener { |
|
GameManager gameManager; |
|
GameFrame gameFrame; |
|
Controller() { |
|
gameManager = new GameManager(); |
|
gameManager.addPropertyChangeListener1(this); |
|
gameManager.addPropertyChangeListener2(this); |
|
gameFrame = new GameFrame(gameManager.getCardDeck()); |
|
gameFrame.startPanel.button.addActionListener(actionEvent -> gameStart()); |
|
} |
|
|
|
private void gameStart() { |
|
String player1 = gameFrame.startPanel.field1.getText(); |
|
String player2 = gameFrame.startPanel.field2.getText(); |
|
|
|
|
|
if(checkStringLength(player1) || checkStringLength(player2) || checkStringValidity(player1, player2)) { |
|
return; |
|
} |
|
|
|
makePlayers(player1, player2); |
|
showGameScreen(); |
|
} |
|
|
|
private boolean checkStringLength(String player) { |
|
if(player.length() > 7) { |
|
JOptionPane.showMessageDialog(null, "7글자 이하로 이름을 입력해 주세요.", "정보",JOptionPane.INFORMATION_MESSAGE); |
|
gameFrame.startPanel.field1.removeAll(); |
|
return true; |
|
} |
|
return false; |
|
} |
|
private boolean checkStringValidity(String player1, String player2) { |
|
if(isStringEmpty(player1) || isStringEmpty(player2)) { |
|
return true; |
|
} else return haveSameName(player1, player2); |
|
} |
|
private boolean isStringEmpty(String player) { |
|
if(player.isEmpty()) { |
|
JOptionPane.showMessageDialog(null, "이름을 채워주세요.", "정보",JOptionPane.INFORMATION_MESSAGE); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
private boolean haveSameName(String player1, String player2) { |
|
if(player1.equals(player2)) { |
|
JOptionPane.showMessageDialog(null, "같은 이름을 입력할 수 없습니다.", "정보",JOptionPane.INFORMATION_MESSAGE); |
|
gameFrame.startPanel.field1.removeAll(); |
|
gameFrame.startPanel.field2.removeAll(); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
private void makePlayers(String playerName1, String playerName2) { |
|
gameManager.doMakePlayer(1, playerName1); |
|
gameManager.doMakePlayer(2, playerName2); |
|
} |
|
|
|
private void showGameScreen() { |
|
gameFrame.gamePanel.updateCurrentStatus(gameManager.getWhosTurn().getName(), gameManager.getWhosTurn().getScore(), |
|
gameManager.getWhosTurn().getCombo(), gameManager.getTrying(), gameManager.countActive()); |
|
gameFrame.turnToGameScreen(); |
|
} |
|
@Override |
|
public void propertyChange(PropertyChangeEvent evt) { |
|
if("Phase Change".equals(evt.getPropertyName())) { |
|
gameFrame.gamePanel.updateCurrentStatus(gameManager.getWhosTurn().getName(), gameManager.getWhosTurn().getScore(), |
|
gameManager.getWhosTurn().getCombo(), gameManager.getTrying(), gameManager.countActive()); |
|
|
|
gameFrame.refresh(); |
|
} else if("Game Clear".equals(evt.getPropertyName())) { |
|
Result result = gameManager.makeResult(); |
|
gameFrame.resultPanel = new ResultPanel(OutputManager.getResult(result), OutputManager.getPoint1(result), OutputManager.getPoint2(result)); |
|
gameFrame.showResult(); |
|
} |
|
|
|
} |
|
} |
|
|
|
class CardManager { |
|
public static final int MAX_CARD_NUMBER = 8; |
|
public static final int CARD_PAIR = 3; |
|
public static final int ROW = 6; |
|
public static final int COLUMN = 3; |
|
public static final int HOW_MANY_CARDS_TO_PICK = ROW * COLUMN; |
|
|
|
private CardManager() {} |
|
|
|
public static List<Card> makeDeck(GameManager gameManager) { |
|
List<Card> cards = makeCards(gameManager); |
|
|
|
Collections.shuffle(cards); |
|
|
|
return cards.subList(0, HOW_MANY_CARDS_TO_PICK); |
|
} |
|
|
|
private static List<Card> makeCards(GameManager gameManager) { |
|
List<Card> cards = new ArrayList<>(); |
|
|
|
for(int i = 0;i < CARD_PAIR; i++) { |
|
for(int j = 1; j < MAX_CARD_NUMBER+1; j++) { |
|
cards.add(new Card(j, gameManager)); |
|
} |
|
} |
|
return cards; |
|
} |
|
} |
|
|
|
class Card extends JButton{ |
|
private final Integer number; |
|
private Boolean status; |
|
private final ImageIcon image; |
|
private final ImageIcon cover; |
|
|
|
Card(int number, GameManager gameManager) { |
|
this.number = number; |
|
this.status = Boolean.TRUE; |
|
this.image = new ImageIcon(Objects.requireNonNull(getClass().getResource("/" + number + ".png"))); |
|
this.cover = new ImageIcon(Objects.requireNonNull(getClass().getResource("/" + 0 + ".png"))); |
|
this.setIcon(cover); |
|
this.addActionListener(e -> gameManager.cardClicked(Card.this)); |
|
} |
|
|
|
public Boolean getStatus() { |
|
return this.status; |
|
} |
|
public Integer getNumber() { |
|
return this.number; |
|
} |
|
public void changeStatus() { |
|
this.status = !Boolean.TRUE.equals(this.status); |
|
} |
|
public void showCard() { |
|
this.setIcon(this.image); |
|
} |
|
public void coverCard() { |
|
this.setIcon(this.cover); |
|
} |
|
|
|
@Override |
|
public boolean equals(Object obj) { |
|
// 같은 객체는 포함하지 않도록 override |
|
// if(this == obj) return true; |
|
if(obj == null || getClass() != obj.getClass()) return false; |
|
return checkEquals(obj); |
|
} |
|
|
|
private boolean checkEquals(Object obj) { |
|
Card card = (Card) obj; |
|
return number.equals(card.getNumber()) && status.equals(card.getStatus()) && this!=obj; |
|
} |
|
|
|
@Override |
|
public int hashCode() { |
|
return Objects.hash(number, status); |
|
} |
|
} |
|
|
|
class GameManager { |
|
private final List<Card> cardDeck; |
|
private int trying; |
|
private Player player1; |
|
private Player player2; |
|
private Player whosTurn; |
|
private Boolean consecutive; |
|
private Card card1; |
|
private Card card2; |
|
private final PropertyChangeSupport pcs1 = new PropertyChangeSupport(this); |
|
private final PropertyChangeSupport pcs2 = new PropertyChangeSupport(this); |
|
|
|
|
|
GameManager() { |
|
this.cardDeck = CardManager.makeDeck(this); |
|
this.trying = 0; |
|
this.player1 = null; |
|
this.player2 = null; |
|
this.whosTurn = null; |
|
this.consecutive = false; |
|
this.card1 = null; |
|
this.card2 = null; |
|
} |
|
|
|
public void doMakePlayer(int gamerNum, String playerName) { |
|
if(gamerNum == 1) { |
|
this.player1 = new Player(playerName); |
|
} else { |
|
this.player2 = new Player(playerName); |
|
this.whosTurn = player1; |
|
} |
|
} |
|
public List<Card> getCardDeck() { |
|
return this.cardDeck; |
|
} |
|
public void cardClicked(Card cardClicked) { |
|
if(card1 == null) { |
|
makeCard1(cardClicked); |
|
} else if(card2 == null) { |
|
makeCard2(cardClicked); |
|
} |
|
} |
|
|
|
private void makeCard1(Card cardClicked) { |
|
if(!Boolean.TRUE.equals(cardClicked.getStatus())){// 이미 풀린 카드: 아무것도 하지 않는다. |
|
return; |
|
} |
|
card1 = cardClicked; |
|
card1.showCard(); |
|
} |
|
|
|
private void makeCard2(Card cardClicked) { |
|
if(cardClicked == card1) { |
|
card1.coverCard(); |
|
card1 = null; |
|
return; |
|
} |
|
if(!Boolean.TRUE.equals(cardClicked.getStatus())){// 이미 풀린 카드: 아무것도 하지 않는다. |
|
return; |
|
} |
|
card2 = cardClicked; |
|
card2.showCard(); |
|
checkCard(card1.getNumber(), card2.getNumber()); |
|
} |
|
public Player getWhosTurn() { |
|
return this.whosTurn; |
|
} |
|
|
|
private void changeTurn() { |
|
if(this.whosTurn == this.player1) { |
|
//턴 바꿀땐 무조건 deactivate |
|
whosTurn.updateCombo(deactivateConsecutive()); |
|
this.whosTurn = this.player2; |
|
} else { |
|
whosTurn.updateCombo(deactivateConsecutive()); |
|
this.whosTurn = this.player1; |
|
} |
|
} |
|
|
|
private void activateConsecutive() { |
|
this.consecutive = true; |
|
} |
|
|
|
private boolean deactivateConsecutive() { |
|
this.consecutive = false; |
|
return false; |
|
} |
|
|
|
public void checkCard(Integer cardNumber1, Integer cardNumber2) { |
|
if(cardNumber1.equals(cardNumber2)) { |
|
card1.changeStatus(); |
|
card2.changeStatus(); |
|
|
|
whosTurn.updateCombo(consecutive); |
|
whosTurn.setScore(); //현재 플레이어의 점수를 업데이트 |
|
activateConsecutive(); // 콤보를 위해 활성화 |
|
if(!isItOver()) { |
|
doGame(); |
|
} else { |
|
pcs2.firePropertyChange("Game Clear", null, null); |
|
} |
|
return; |
|
} |
|
card2.showCard(); |
|
changeTurn(); |
|
doGame(); |
|
} |
|
|
|
public void doGame() { |
|
Timer timer = new Timer(2000, taskPerformer); |
|
timer.setRepeats(false); |
|
timer.start(); |
|
} |
|
ActionListener taskPerformer = actionEvent -> changePhase(); |
|
|
|
public void changePhase() { |
|
resetCards(); |
|
tryMore(); |
|
pcs1.firePropertyChange("Phase Change", null, null); |
|
} |
|
|
|
private void resetCards() { |
|
card1 = null; |
|
card2 = null; |
|
for(Card card: cardDeck) { |
|
if(card.getStatus() == Boolean.FALSE) { |
|
card.showCard(); |
|
} else { |
|
card.coverCard(); |
|
} |
|
} |
|
} |
|
private void tryMore() { |
|
this.trying += 1; |
|
} |
|
public int getTrying() { |
|
return this.trying; |
|
} |
|
|
|
public boolean isItOver() { |
|
for(Card card : cardDeck) { |
|
if(isCardActive(card)) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
private boolean isCardActive(Card card) { |
|
if(Boolean.TRUE.equals(card.getStatus())) { |
|
return checkIdenticalTwin(card); |
|
} |
|
return false; |
|
} |
|
|
|
private boolean checkIdenticalTwin(Card card) { |
|
// Card 클래스의 equal 재정의로 인한 간소화 |
|
return cardDeck.contains(card); |
|
} |
|
|
|
public int countActive() { |
|
int count = 0; |
|
for(Card card : cardDeck) { |
|
if(Boolean.TRUE.equals(card.getStatus())) { |
|
count += 1; |
|
} |
|
} |
|
return count; |
|
} |
|
|
|
public Result makeResult() { |
|
return new Result(player1, player2); |
|
} |
|
|
|
public void addPropertyChangeListener1(PropertyChangeListener listener) { |
|
pcs1.addPropertyChangeListener(listener); |
|
} |
|
public void addPropertyChangeListener2(PropertyChangeListener listener) { |
|
pcs2.addPropertyChangeListener(listener); |
|
} |
|
} |
|
|
|
class Result { |
|
private Player winner; |
|
private Player another; |
|
private boolean draw; |
|
|
|
public Result(Player player1, Player player2) { |
|
if(player1.getScore() > player2.getScore()) { |
|
this.winner = player1; |
|
this.another = player2; |
|
this.draw = false; |
|
} else if(player2.getScore() > player1.getScore()) { |
|
this.winner = player2; |
|
this.another = player1; |
|
this.draw = false; |
|
} else { |
|
this.draw = true; |
|
this.winner = player1; |
|
this.another = player2; |
|
} |
|
} |
|
public Player getWinner() { |
|
return this.winner; |
|
} |
|
public Player getAnother() { |
|
return this.another; |
|
} |
|
public boolean isItDraw() { |
|
return this.draw; |
|
} |
|
} |
|
|
|
class OutputManager { |
|
private OutputManager() {} |
|
|
|
public static String getResult(Result result) { |
|
if(!result.isItDraw()) { |
|
return "<html>축하합니다! 승자는 "+result.getWinner().getName()+" 입니다.</html>"; |
|
} else { |
|
return "<html>비겼습니다.</html>"; |
|
} |
|
} |
|
public static String getPoint1(Result result) { |
|
return "<html>"+result.getWinner().getName() +": "+ result.getWinner().getScore()+" 점</html>"; |
|
} |
|
public static String getPoint2(Result result) { |
|
return "<html>"+result.getAnother().getName() +": "+ result.getAnother().getScore()+" 점</html>"; |
|
} |
|
|
|
public static String printGameStatus(String name, Integer score, Integer combo, Integer trying, Integer activeCount) { |
|
return "<html> "+name +"의 차례입니다. 현재 점수: " |
|
+ score+", 콤보: x"+combo + "<br><시도 " + trying+", 남은 카드: "+ |
|
activeCount+"></html>"; |
|
} |
|
} |
|
|
|
class Player { |
|
private static final Integer GAME_SCORE_BASE = 10; |
|
private Integer score; |
|
private final String name; |
|
private Integer combo; |
|
|
|
public Player(String name) { |
|
this.name = name; |
|
this.score = 0; |
|
this.combo = 1; |
|
} |
|
|
|
public void updateCombo(boolean consecutive) { |
|
if(consecutive) { |
|
this.combo *= 2; |
|
return; |
|
} |
|
this.combo = 1; |
|
} |
|
public Integer getScore() { |
|
return this.score; |
|
} |
|
|
|
public Integer getCombo() { return this.combo; } |
|
|
|
public String getName() { |
|
return this.name; |
|
} |
|
|
|
public void setScore() { |
|
this.score += this.combo * GAME_SCORE_BASE; |
|
} |
|
} |
|
|
|
class GameFrame extends JFrame { |
|
StartPanel startPanel; |
|
GamePanel gamePanel; |
|
ResultPanel resultPanel; |
|
|
|
public GameFrame(List<Card> cardDeck) { |
|
setTitle("카드 맞추기 게임"); |
|
setSize(500, 500); |
|
this.startPanel = new StartPanel(); |
|
add(startPanel); |
|
setVisible(true); |
|
this.gamePanel = new GamePanel(cardDeck); |
|
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); |
|
} |
|
|
|
public void turnToGameScreen() { |
|
remove(startPanel); |
|
add(gamePanel); |
|
revalidate(); |
|
repaint(); |
|
} |
|
|
|
public void refresh() { |
|
revalidate(); |
|
repaint(); |
|
} |
|
|
|
public void showResult(){ |
|
remove(gamePanel); |
|
add(resultPanel); |
|
revalidate(); |
|
repaint(); |
|
} |
|
} |
|
|
|
class StartPanel extends JPanel { |
|
JLabel name1 = new JLabel("Player 1"); |
|
JLabel name2 = new JLabel("Player 2"); |
|
JLabel introduction = new JLabel("<html>안녕하세요. 카드게임을 시작하겠습니다.<br>시작하기 전에 플레이어 두 명의 이름을 입력해 주세요.<br></html>"); |
|
JTextField field1 = new JTextField(15); |
|
JTextField field2 = new JTextField(15); |
|
JButton button = new JButton("게임 시작!"); |
|
public StartPanel() { |
|
this.makeStartPanelLayout(); |
|
} |
|
public void makeStartPanelLayout() { |
|
this.setLayout(new BorderLayout()); |
|
this.add(introduction, BorderLayout.NORTH); |
|
|
|
JPanel centralPanel = new JPanel(); |
|
centralPanel.setLayout(new BoxLayout(centralPanel, BoxLayout.X_AXIS)); |
|
|
|
makeNamePanel(centralPanel); |
|
|
|
JPanel coverPanel = makeSomeSpace(centralPanel); |
|
|
|
this.add(coverPanel, BorderLayout.CENTER); |
|
this.add(button, BorderLayout.SOUTH); |
|
} |
|
|
|
private void makeNamePanel(JPanel centralPanel) { |
|
JPanel panelOne = new JPanel(); |
|
panelOne.add(name1); |
|
panelOne.add(field1); |
|
field1.setMaximumSize(field1.getPreferredSize()); |
|
|
|
JPanel panelTwo = new JPanel(); |
|
panelTwo.add(name2); |
|
panelTwo.add(field2); |
|
field2.setMaximumSize(field2.getPreferredSize()); |
|
|
|
centralPanel.add(panelOne); |
|
centralPanel.add(panelTwo); |
|
} |
|
private JPanel makeSomeSpace(JPanel centralPanel) { |
|
JPanel outerPanel = new JPanel(); |
|
outerPanel.setLayout(new BoxLayout(outerPanel, BoxLayout.Y_AXIS)); |
|
outerPanel.add(Box.createVerticalGlue()); |
|
outerPanel.add(centralPanel); |
|
outerPanel.add(Box.createVerticalGlue()); |
|
return outerPanel; |
|
} |
|
} |
|
|
|
class GamePanel extends JPanel { |
|
JLabel introduction = new JLabel(); |
|
JPanel cardGridPanel = new JPanel(new GridLayout(3, 6)); |
|
|
|
public GamePanel(List<Card> cardDeck) { |
|
setLayout(); |
|
makeCardGrid(cardDeck); |
|
} |
|
|
|
public void updateCurrentStatus(String name, Integer score, Integer combo, Integer trying, Integer activeCount) { |
|
introduction.setText(OutputManager.printGameStatus(name, score, combo, trying, activeCount)); |
|
} |
|
private void setLayout() { |
|
this.setLayout(new BorderLayout()); |
|
this.add(introduction, BorderLayout.NORTH); |
|
this.add(cardGridPanel, BorderLayout.CENTER); |
|
} |
|
private void makeCardGrid(List<Card> cardDeck) { |
|
for(int i = 0; i < 18; i++) { |
|
cardGridPanel.add(cardDeck.get(i)); |
|
} |
|
} |
|
} |
|
|
|
class ResultPanel extends JPanel { |
|
JLabel introduction = new JLabel("<게 임 결 과>"); |
|
JLabel winner = new JLabel(); |
|
JLabel player1Result = new JLabel(); |
|
JLabel player2Result = new JLabel(); |
|
|
|
ResultPanel(String winner, String result1, String result2) { |
|
setFonts(); |
|
setLayout(winner, result1, result2); |
|
makeComponent(); |
|
} |
|
private void setFonts() { |
|
Font font = new Font("Arial", Font.BOLD, 20); |
|
this.winner.setFont(font); |
|
this.player1Result.setFont(font); |
|
this.player2Result.setFont(font); |
|
} |
|
private void setLayout(String winner, String result1, String result2) { |
|
this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); |
|
this.winner.setText(winner); |
|
this.winner.setAlignmentX(Component.CENTER_ALIGNMENT); |
|
this.winner.setMaximumSize(new Dimension(40*10, this.winner.getMinimumSize().height)); |
|
|
|
this.player1Result.setText(result1); |
|
this.player1Result.setAlignmentX(Component.CENTER_ALIGNMENT); |
|
this.player1Result.setMaximumSize(new Dimension(25*10, this.player1Result.getMinimumSize().height)); |
|
|
|
this.player2Result.setText(result2); |
|
this.player2Result.setAlignmentX(Component.CENTER_ALIGNMENT); |
|
this.player2Result.setMaximumSize(new Dimension(25*10, this.player2Result.getMinimumSize().height)); |
|
} |
|
|
|
private void makeComponent() { |
|
this.add(Box.createVerticalGlue()); |
|
this.add(this.winner); |
|
this.add(Box.createRigidArea(new Dimension(0, 10))); |
|
this.add(this.player1Result); |
|
this.add(Box.createRigidArea(new Dimension(0, 10))); |
|
this.add(this.player2Result); |
|
this.add(Box.createVerticalGlue()); |
|
|
|
} |
|
} |