There is a mysterious inversion happening when i disable buttons for users to press or not press. the project is to setup a battleship game and have one side be for a computer and the other for the user. When i have the so the user can press both buttons the ships show up properly on the buttons as hits. but when the computer shoots the buttons a mysteriously inverted 90 degrees south. here is the main part of the code and the AI class too
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
/**
* #author Alex Larson
* Project #3
* This is the main part of the BattleshipGUI.
* This class has several inner classes that are listeners.
* This class also has the main method in it.
* This class sets up the entire GUI.
*/
public class BsGui extends JFrame {
/**Array of FireButtons for the player */
FireButton play[][] = null;
/**Array of FireButtons for the player */
FireButton opp[][] = null;
/**A static constant for the player's BattleshipBoard */
public static BattleshipBoard playerB;
/**A static constant for the opponent's (or computer's) BattleshipBoard */
public static BattleshipBoard opponentB;
/**A static constant for a computer AI object */
public static ComputerAI AI;
/**A static constant that stores the computers designated AI */
public static char computer;
/**public int for passing around the shot the computer takes */
public int[][] compShot;
/**BorderLayout for the JFrame */
public BorderLayout br;
/**GridLayout for main JPanel */
public GridLayout gr;
/**GridLayout for both FireButton holding JPanels */
public GridLayout gr2;
/**GridLayout for bottom JPanel */
public GridLayout gr3;
/**FlowLayout: not used */
public FlowLayout fl;
/**JPanel that has the players FireButtons in it */
public JPanel playB;
/**JPanel that has the opponent's (or computer's) FireButtons in it */
public JPanel oppB;
/**JPanel that is not needed but hold the 2 FireButton holding JPanels */
public JPanel main;
/**JPanel that holds the title JLabel */
public JPanel top;
/**JPanel that hold shipsLeft button and ships JLabel */
public JPanel bottom;
/**Another not used, unneeded JPanel */
public JPanel left;
/**Another not used, unneeded JPanel */
public JPanel right;
/**JButton used to show user number of ships left */
public JButton shipsLeft;
/**JLabel used to show the title of the game */
public JLabel title;
/**JLabel used when game is over */
public JLabel win;
/**JLabel used to the user the number of ships left */
public JLabel ships;
/**Font used to change styles text */
public Font font = null;
/**
* Constructor for BsGui. It creates a GUI with JLabel as a center
* title and has 2 JPanels that hold arrays of FireButtons. It
* also sets up a button at the bottom that gives you the
* number of ships left for the players board, not the computers board.
*/
public BsGui(){
//Instantiating Section
play = new FireButton[10][10];
opp = new FireButton[10][10];
br = new BorderLayout(10,10);
gr = new GridLayout(1,2);
gr2 = new GridLayout(10,10);
gr3 = new GridLayout(2,2);
fl = new FlowLayout();
playB = new JPanel();
oppB = new JPanel();
main = new JPanel();
top = new JPanel();
bottom = new JPanel();
left = new JPanel();
right = new JPanel();
shipsLeft = new JButton();
title = new JLabel();
win = new JLabel();
ships = new JLabel();
font = new Font("Veranda", Font.BOLD, 20);
compShot = new int[2][1];
//Setting parameters for GUI
main.setBackground(Color.white);
main.setLayout(gr);
top.add(title);
playB.setBackground(Color.cyan);
playB.setName("Player Board");
playB.setLayout(gr2);
oppB.setBackground(Color.magenta);
oppB.setName("Computer Board");
oppB.setLayout(gr2);
shipsLeft.setText("How Many Ships Left");
shipsLeft.setVisible(true);
title.setText("Battleship");
title.setFont(font);
this.setTitle("Battlseship");
this.setSize(1100, 700);
this.setLayout(br);
//Adding Listeners
shipsLeft.addActionListener(new ShipsLeftListener());
//Adding components to GUI section
this.add(main, BorderLayout.CENTER);
this.add(top, BorderLayout.NORTH);
this.add(bottom, BorderLayout.SOUTH);
this.add(left, BorderLayout.WEST);
this.add(right, BorderLayout.EAST);
this.addFireBottonsToArray();
this.addFireButtonsPlay();
this.addFireButtonsOpp();
main.add(playB);
main.add(oppB);
bottom.add(shipsLeft);
bottom.add(ships);
}
/**
* This method adds new FireButtons to each part of the
* FireButton arrays. The first for loop adds buttons for the
* player. The second for loop adds buttons for the computer
* and makes them not click-able.
*/
private void addFireBottonsToArray(){
//Adds FireButtons to play array
for(int i = 0; i < 10; i++){
for(int j = 0; j < 10; j++){
play[j][i] = new FireButton(j, i);
}
}
//Adds FireButtons to opp array
for(int i = 0; i < 10; i++){
for(int j = 0; j < 10; j++){
opp[j][i] = new FireButton(j, i);
//opp[j][i].setEnabled(false);
}
}
}
/**
* This method adds and listener to each FireButton
* before adding it to the JPanel for the player.
*/
private void addFireButtonsPlay(){
for(int i = 0; i < 10; i++){
for(int j = 0; j < 10; j++){
play[j][i].addActionListener( new FireButtonListener());
playB.add(play[j][i]);
}
}
}
/**
* This method adds and listener to each FireButton
* before adding it to the JPanel for the computer.
*/
private void addFireButtonsOpp(){
for(int i = 0; i < 10; i++){
for(int j = 0; j < 10; j++){
opp[j][i].addActionListener( new OpponentButtonListener());
oppB.add(opp[j][i]);
}
}
}
/**
* This method hits buttons for the computer where ever
* the AI tells it to. Int x is the row of the button and
* int y is the column.
*
* #param int x
* The row coordinate.
* #param int y
* The column coordinate.
*/
public void computerButtonHitter(int x, int y){
if(opponentB.fireShot(x, y) == true){
opp[x][y].setBackground(Color.RED);
opp[x][y].setFont(font);
opp[x][y].setText("H");
}
else{
opp[x][y].setBackground(Color.BLUE);
opp[x][y].setFont(font);
opp[x][y].setText("M");
}
}
/**
* This inner class is for the FireButton Listener. This particular
* class does probably too much but it was an easy way to do the
* problem at hand.
*/
public class FireButtonListener implements ActionListener{
/**
* This method is mandatory with implementing ActionListener.
* When a FireButton is pressed it finds the place it was fired at
* and changes it to red if a ship exists at that spot and if
* not turns it blue. So red means hit and blue means miss. This
* method also adds an 'H' for hit and 'M' for miss to the button.
* This method then also calls the method to make the computer
* do its move based on a switch case for which AI to use.
*
* #param ActionEvent
* The event that triggers this method.
*/
public void actionPerformed(ActionEvent e){
if(e.getSource() instanceof FireButton){
FireButton fb = (FireButton)e.getSource();
//System.out.println("FireButton at "+(fb.getCell().getColumn()+1)+", "+(fb.getCell().getRow()+1)+" was pressed");
if(playerB.fireShot(fb.getCell().getColumn(), fb.getCell().getRow()) == true){
play[fb.getCell().getRow()][fb.getCell().getColumn()].setBackground(Color.RED);
play[fb.getCell().getRow()][fb.getCell().getColumn()].setFont(font);
play[fb.getCell().getRow()][fb.getCell().getColumn()].setText("H");
play[fb.getCell().getRow()][fb.getCell().getColumn()].setEnabled(false);
}
else{
play[fb.getCell().getRow()][fb.getCell().getColumn()].setBackground(Color.BLUE);
play[fb.getCell().getRow()][fb.getCell().getColumn()].setFont(font);
play[fb.getCell().getRow()][fb.getCell().getColumn()].setText("M");
play[fb.getCell().getRow()][fb.getCell().getColumn()].setEnabled(false);
}
switch(computer){
case 'r':
compShot = AI.randomShot();
computerButtonHitter(compShot[0][0], compShot[1][0]);
break;
case 's':
compShot = AI.systematicShot();
computerButtonHitter(compShot[0][0], compShot[1][0]);
break;
}
if(playerB.isGameOver() == true){
main.remove(playB);
main.remove(oppB);
font = new Font("Veranda", Font.BOLD, 50);
win.setFont(font);
win.setText("Congratulations u win!");
main.add(win);
}
if(opponentB.isGameOver() == true){
main.remove(playB);
main.remove(oppB);
font = new Font("Veranda", Font.BOLD, 50);
win.setFont(font);
win.setText("Awe too bad, the computer won");
main.add(win);
}
}
}
}
/**
* This Class is simple and offers the user a way finding how
* many ships he or she still needs to destroy.
*/
public class ShipsLeftListener implements ActionListener{
/**
* This method is mandatory when implementing ActionListener.
* It sets the JLabel ships to the number of ships left
* when the button is pressed.
*
* #param ActionEvent
* The event that triggers this method.
*/
public void actionPerformed(ActionEvent e){
ships.setText("" + playerB.getNumBattleshipsLeft());
}
}
/**
* This class works exactly as the first part of FireButtonListener.
* The only difference is it doesn't work now since all the
* opponent's (computer) buttons are disabled. This method is here
* for later use for 2 player mode. (Did not have time to get that far).
*/
public class OpponentButtonListener implements ActionListener{
/**
* This method is mandatory with implementing ActionListener.
* When a FireButton is pressed it finds the place it was fired at
* and changes it to red if a ship exists at that spot and if
* not turns it blue. So red means hit and blue means miss. This
* method also adds an 'H' for hit and 'M' for miss to the button.
*
* #param ActionEvent
* The event that triggers this method.
*/
public void actionPerformed(ActionEvent e){
if(e.getSource() instanceof FireButton){
FireButton fb = (FireButton)e.getSource();
//System.out.println("Opponent Button Pressed at "+(fb.getCell().getColumn()+1)+", "+(fb.getCell().getRow()+1)+" was pressed");
if(opponentB.fireShot(fb.getCell().getColumn(), fb.getCell().getRow()) == true){
opp[fb.getCell().getRow()][fb.getCell().getColumn()].setBackground(Color.RED);
opp[fb.getCell().getRow()][fb.getCell().getColumn()].setFont(font);
opp[fb.getCell().getRow()][fb.getCell().getColumn()].setText("H");
opp[fb.getCell().getRow()][fb.getCell().getColumn()].setEnabled(false);
}
else{
opp[fb.getCell().getRow()][fb.getCell().getColumn()].setBackground(Color.BLUE);
opp[fb.getCell().getRow()][fb.getCell().getColumn()].setFont(font);
opp[fb.getCell().getRow()][fb.getCell().getColumn()].setText("M");
opp[fb.getCell().getRow()][fb.getCell().getColumn()].setEnabled(false);
}
}
}
}
/**
* Main Function
*
* The main runs everything as normal. :P
* It creates a GUI, an AI, and 2 BattleshipBoards.
*
* #param args
* Command line arguments
* For program to work correctly it
* requires the first arg to be a path to
* the player's ship placements. The second
* arg requires the same as the first but for the
* opponent. The 3rd arg requires a single letter
* that represents the chosen AI for the computer.
* The only supported letters at the moment are 'r' and 's'.
* 'r' is random and 's' is systematic. Other computer AIs and
* difficulties will be added later.
*/
public static void main(String[] args) {
String player;
String opponent;
String inputAI;
File fPlay;
File fOpp;
player = args[0];
opponent = args[1];
inputAI = args[2];
computer = inputAI.charAt(0);
AI = new ComputerAI(computer);
try{
fPlay = new File(player);
fOpp = new File(opponent);
playerB = new BattleshipBoard(fPlay);
opponentB = new BattleshipBoard(fOpp);
}
catch(FileNotFoundException fne){
System.out.println("File path given must be wrong or file doesnt exist");
System.exit(0);
}
catch(Exception e){
System.exit(0);
}
JFrame gui = new BsGui();
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.setVisible(true);
}
}
import java.util.Random;
/**
*
* #author Alex Larson XPS
*
*This class is how the computer gets its brains.
*Has a constructor to set up the computer and 2 brain methods.
*One method gives random shots and the other gives systematic shots.
*More brain options to be added later.
*/
public class ComputerAI {
/**This hold the last shot X coordinate */
public int lastShotX;
/**This hold the last shot Y coordinate */
public int lastShotY;
/**Not used, but will be for smarter AI
* will be for determining if last shot was a hit or not */
public boolean wasLastShotHit;
/**Not used, but will be for smarter AI,
* will be for holding last X hit */
public int lastHitX;
/**Not used, but will be for smarter AI,
* will be for holding last Y hit */
public int lastHitY;
/**Not used, but would be for keeping track of shots fired */
public int shotCounter;
/**Used for passing on the coordinates of the shot fired */
public int shotFired[][];
/**Used to keep track of all shots fired coordinates */
public int shotsFired[][];
/**The random number generator */
public Random gen;
/**Keeps track of the computer AI */
public char typeOfAI;
/**
* This is the computerAI constructor.
* It initializes all variables and
* gets the random generator ready to use.
*
* #param c
* This is the char that determines the type of AI to be used.
*/
public ComputerAI(char c){
lastShotX = -1;
lastShotY = -1;
wasLastShotHit = false;
lastHitX = -1;
lastHitY = -1;
shotCounter = 0;
//At 0,0 shotX will be stored and at 1,0 shotY will be stored
shotFired = new int[2][1];
//There are 100 total shots on a 10x10 board
//So this will hold 100 coordinates of shots
shotsFired = new int[100][2];
for(int i = 0; i < 100; i++){
int j = 0;
shotsFired[i][j] = -1;
shotsFired[i][j+1] = -1;
}
//Initializes the random number generator
gen = new Random(9);
typeOfAI = c;
}
/**
* Grabs the char representing the AI and returns it
*
* #return the type of AI being used
*/
public char getTypeOfAI(){
return typeOfAI;
}
/**
* This is the random shot brain.
* It uses a random number generator to makes random shots.
* It also keeps track of previous shots taken in shotsFired.
* After making sure the shot has been taken before it returns
* those coordinates.s
*
* #return the int[][] that represents the coordinates of the shot fired
*/
public int[][] randomShot(){
int hitX = gen.nextInt(9);
int hitY = gen.nextInt(9);
shotFired[0][0] = hitX;
shotFired[1][0] = hitY;
int i;
for(i = 0; i < 100; i++){
//This if statement is for setting that this shot has been taken
if(shotsFired[i][0] == -1){
shotsFired[i][0] = hitX;
shotsFired[i][1] = hitY;
break;
}
//This if statement is to make sure the shot being generated
//has not been taken yet.
if(shotsFired[i][0] == hitX && shotsFired[i][1] == hitY){
hitX = gen.nextInt(9);
hitY = gen.nextInt(9);
shotFired[0][0] = hitX;
shotFired[1][0] = hitY;
}
}
System.out.println(hitX +" "+ hitY);
return shotFired;
}
/**
* This is the systematic brain.
* It marches along row by row to fire shots.
* It keeps track of the last shot taken by using
* lastShotX and lastShotY. It then return the
* shot fired.
*
* #return the int[][] that represents the coordinates of the shot fired
*/
public int[][] systematicShot(){
if(lastShotX == -1 && lastShotY == -1){
lastShotX = 0;
lastShotY = 0;
shotFired[0][0] = lastShotX;
shotFired[1][0] = lastShotY;
return shotFired;
}
for(int i = 0; i < 10; i++){
if(lastShotX == -1){
lastShotX = 0;
shotFired[0][0] = lastShotX;
shotFired[1][0] = lastShotY;
return shotFired;
}
for(int j = 0; j < 10; j++){
if(lastShotX == j && lastShotY == i){
if(lastShotX == 9){
lastShotX = -1;
lastShotY++;
}
else{
lastShotX = j+1;
shotFired[0][0] = lastShotX;
shotFired[1][0] = lastShotY;
return shotFired;
}
}
}
}
return shotFired;
}
}
If you compare your method computerButtonHitter
if(opponentB.fireShot(x, y) == true){
opp[x][y].setBackground(Color.RED);
opp[x][y].setFont(font);
opp[x][y].setText("H");
}
else{
opp[x][y].setBackground(Color.BLUE);
opp[x][y].setFont(font);
opp[x][y].setText("M");
}
with the FireButtonListener
if(playerB.fireShot(fb.getCell().getColumn(), fb.getCell().getRow()) == true){
play[fb.getCell().getRow()][fb.getCell().getColumn()].setBackground(Color.RED);
play[fb.getCell().getRow()][fb.getCell().getColumn()].setFont(font);
play[fb.getCell().getRow()][fb.getCell().getColumn()].setText("H");
play[fb.getCell().getRow()][fb.getCell().getColumn()].setEnabled(false);
}
else{
play[fb.getCell().getRow()][fb.getCell().getColumn()].setBackground(Color.BLUE);
play[fb.getCell().getRow()][fb.getCell().getColumn()].setFont(font);
play[fb.getCell().getRow()][fb.getCell().getColumn()].setText("M");
play[fb.getCell().getRow()][fb.getCell().getColumn()].setEnabled(false);
}
you'll find a difference. In short form the first has opponentB.fireShot(x,y) together with opp[x][y].... while the second has playerB.fireShot(c,r) and play[r][c].... so while the first has same order in method and array the second has not. I suppose you should swith one of those in order to remain consistent with each other. So either opp[y][x] or play[c][r] but I cannot judge which one is correct (because I do not have full code).
Related
Working on a program that consists of emoji's and you get an emoji that you can shoot, bit like Angry Birds but way more basic. At the moment the stand still emoji is falling down (increasing vertical velocity by gravity until it hits the ground). However this only happens when the main object has a greater horizontal position than the stand still object.
/**
* Represents an emoji that can move on a window.
*/
public class Emoji{
// Constants for all Emoji: size, position of the ground
public static final double SIZE = 40; // width/length of the emoji images
public static final double GROUND = EmojiLauncher.GROUND;
public static final double GRAVITY = 0.25; // how much to reduce the speed each step.
// Fields to store state of the Emoji:
private double horizontalPos;
private double aboveGround;
private double hSpeed;
private double vSpeed;
private String name;
// Constructor
/**
* Constructor
*/
public Emoji(double x, double h, String name){
this.horizontalPos = x;
this.aboveGround = h;
this.name = "emojis/"+name;
}
// Methods
/**
* Draws an emoji
*/
public void draw(){
UI.drawImage(this.name, this.horizontalPos, this.aboveGround , SIZE, SIZE);
}
/**
* Makes emoji take a step
*/
public void step(){
if(this.vSpeed != 0 || this.hSpeed != 0){
this.horizontalPos += hSpeed;
this.aboveGround += vSpeed;
this.vSpeed += GRAVITY;
if(this.aboveGround > GROUND - SIZE){
vSpeed = 0;
hSpeed = 0;
this.aboveGround = GROUND - SIZE;
}
}
}
/**
*
* Returns the height of the emoji above the floor.
*/
public double getHeight(){
return this.aboveGround;
}
/**
*
*
* Returns the horizontal position
*/
public double getX(){
return this.horizontalPos;
}
/**
* Returns the speed of the emoji
*/
public double getSpeed(){
return Math.hypot(hSpeed, vSpeed);
}
/**
*
* Set the horizontal and vertical speed of emoji
*/
public void launch(double xSpeed, double ySpeed){
this.hSpeed = xSpeed;
this.vSpeed = ySpeed;
}
/**
* return true if emoji is touching other emoji
*/
public boolean touching(Emoji other){
if(this.horizontalPos >= other.horizontalPos || this.aboveGround >= other.aboveGround){
return true;
}
else {
return false;
}
}
/**
* if emojis hit each other, the hit emoji starts moving
*/
public void bump(Emoji other){
if((this.hSpeed > 0 || vSpeed > 0) &&
(this.horizontalPos >= other.horizontalPos && this.aboveGround >= other.aboveGround)){
other.hSpeed = 1;
}
}
/**
* Main method
*/
public static void main(String[] args){
do {
Emoji bike = new Emoji(110, 150, "bicycle.png");
bike.draw();
Emoji moon = new Emoji(50, 200, "moon.png");
moon.draw();
moon.launch(5, 0);
UI.println("moon launched");
for (int i=0; i<=100; i++){
bike.step();
moon.step();
UI.clearGraphics();
UI.drawLine(0, GROUND, 500, GROUND);
bike.draw();
moon.draw();
if(moon.touching(bike)&& moon.getSpeed()>0){
moon.bump(bike);
UI.println(i+": moon touched and then bumped bike");
}
if(moon.getX() > 55 && moon.getX()<65){
UI.println(i+": moon has moved to the right");
}
if (moon.getHeight()==0){
UI.println(i+": moon at ground");
}
if (bike.getHeight()==0){
UI.println(i+": bike at ground");
}
UI.sleep(100);
if (bike.getHeight()==0 && moon.getHeight()==0){
break;
}
}
} while (UI.askBoolean("test again?"));
}
// END OF DRAFT CODE
}
This is the emoji class
Needing help with the touching and bump methods so that the stand still emoji gets bumped more realistically and based on the speed of the emoji controlled.
this is what it looks like when run
the moon in the bottom left was my object that I shot at the angry target. They both need to stop on the ground.
I have a UI import which makes things much easier to make.
I'm using RSyntaxTextArea for a minimized IDE I'm working on, Everything seems to be really working smoothly except for the line numbering, which I couldn't really make it show:
RSyntaxTextArea textArea = new RSyntaxTextArea(20, 60);
textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_C);
textArea.setCodeFoldingEnabled(true);
textArea.setAntiAliasingEnabled(true);
RTextScrollPane sp = new RTextScrollPane(textArea);
sp.setLineNumbersEnabled(true);
sp.setFoldIndicatorEnabled(true);
if ( sp.getLineNumbersEnabled() )
{
System.out.println("Enabled"); // it prints the line but it's not showing
}
contentPane.add(/*textEditorScrollPane*/ textArea, BorderLayout.CENTER);
I can't figure out why it's not showing the line numbers..
It's not showing the scrollbars either, right? Assuming that contentPane is where you want your components, you need to add the RTextScrollPane instance to the contentPane, not the RSyntaxTextArea instance. The Gutter, which displays line numbers, is a part of the RTextScrollPane - an extended JScrollPane.
If you don't add a scroll pane to your GUI, it will not be shown, nor will you be able to scroll around. :P
So try the following:
contentPane.add(sp, BorderLayout.CENTER);
Alternatively, you can use the following class:
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.*;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
/**
* This class will display line numbers for a related text component. The text
* component must use the same line height for each line. TextLineNumber
* supports wrapped lines and will highlight the line number of the current
* line in the text component.
* <p>
* This class was designed to be used as a component added to the row header
* of a JScrollPane.
*/
public class TextLineNumber extends JPanel
implements CaretListener, DocumentListener, PropertyChangeListener
{
public final static float LEFT = 0.0f;
public final static float CENTER = 0.5f;
public final static float RIGHT = 1.0f;
private final static Border OUTER = new MatteBorder(0, 0, 0, 2, Color.GRAY);
private final static int HEIGHT = Integer.MAX_VALUE - 1000000;
// Text component this TextTextLineNumber component is in sync with
private JTextComponent component;
// Properties that can be changed
private boolean updateFont;
private int borderGap;
private Color currentLineForeground;
private float digitAlignment;
private int minimumDisplayDigits;
// Keep history information to reduce the number of times the component
// needs to be repainted
private int lastDigits;
private int lastHeight;
private int lastLine;
private HashMap<String, FontMetrics> fonts;
/**
* Create a line number component for a text component. This minimum
* display width will be based on 3 digits.
*
* #param component the related text component
*/
public TextLineNumber(JTextComponent component)
{
this(component, 3);
}
/**
* Create a line number component for a text component.
*
* #param component the related text component
* #param minimumDisplayDigits the number of digits used to calculate
* the minimum width of the component
*/
public TextLineNumber(JTextComponent component, int minimumDisplayDigits)
{
this.component = component;
setFont(component.getFont());
setBorderGap(5);
setCurrentLineForeground(Color.RED);
setDigitAlignment(RIGHT);
setMinimumDisplayDigits(minimumDisplayDigits);
component.getDocument().addDocumentListener(this);
component.addCaretListener(this);
component.addPropertyChangeListener("font", this);
}
/**
* Gets the update font property
*
* #return the update font property
*/
public boolean getUpdateFont()
{
return updateFont;
}
/**
* Set the update font property. Indicates whether this Font should be
* updated automatically when the Font of the related text component
* is changed.
*
* #param updateFont when true update the Font and repaint the line
* numbers, otherwise just repaint the line numbers.
*/
public void setUpdateFont(boolean updateFont)
{
this.updateFont = updateFont;
}
/**
* Gets the border gap
*
* #return the border gap in pixels
*/
public int getBorderGap()
{
return borderGap;
}
/**
* The border gap is used in calculating the left and right insets of the
* border. Default value is 5.
*
* #param borderGap the gap in pixels
*/
public void setBorderGap(int borderGap)
{
this.borderGap = borderGap;
Border inner = new EmptyBorder(0, borderGap, 0, borderGap);
setBorder(new CompoundBorder(OUTER, inner));
lastDigits = 0;
setPreferredWidth();
}
/**
* Gets the current line rendering Color
*
* #return the Color used to render the current line number
*/
public Color getCurrentLineForeground()
{
return currentLineForeground == null ? getForeground() : currentLineForeground;
}
/**
* The Color used to render the current line digits. Default is Coolor.RED.
*
* #param currentLineForeground the Color used to render the current line
*/
public void setCurrentLineForeground(Color currentLineForeground)
{
this.currentLineForeground = currentLineForeground;
}
/**
* Gets the digit alignment
*
* #return the alignment of the painted digits
*/
public float getDigitAlignment()
{
return digitAlignment;
}
/**
* Specify the horizontal alignment of the digits within the component.
* Common values would be:
* <ul>
* <li>TextLineNumber.LEFT
* <li>TextLineNumber.CENTER
* <li>TextLineNumber.RIGHT (default)
* </ul>
*
* #param currentLineForeground the Color used to render the current line
*/
public void setDigitAlignment(float digitAlignment)
{
this.digitAlignment =
digitAlignment > 1.0f ? 1.0f : digitAlignment < 0.0f ? -1.0f : digitAlignment;
}
/**
* Gets the minimum display digits
*
* #return the minimum display digits
*/
public int getMinimumDisplayDigits()
{
return minimumDisplayDigits;
}
/**
* Specify the mimimum number of digits used to calculate the preferred
* width of the component. Default is 3.
*
* #param minimumDisplayDigits the number digits used in the preferred
* width calculation
*/
public void setMinimumDisplayDigits(int minimumDisplayDigits)
{
this.minimumDisplayDigits = minimumDisplayDigits;
setPreferredWidth();
}
/**
* Calculate the width needed to display the maximum line number
*/
private void setPreferredWidth()
{
Element root = component.getDocument().getDefaultRootElement();
int lines = root.getElementCount();
int digits = Math.max(String.valueOf(lines).length(), minimumDisplayDigits);
// Update sizes when number of digits in the line number changes
if (lastDigits != digits)
{
lastDigits = digits;
FontMetrics fontMetrics = getFontMetrics(getFont());
int width = fontMetrics.charWidth('0') * digits;
Insets insets = getInsets();
int preferredWidth = insets.left + insets.right + width;
Dimension d = getPreferredSize();
d.setSize(preferredWidth, HEIGHT);
setPreferredSize(d);
setSize(d);
}
}
/**
* Draw the line numbers
*/
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// Determine the width of the space available to draw the line number
FontMetrics fontMetrics = component.getFontMetrics(component.getFont());
Insets insets = getInsets();
int availableWidth = getSize().width - insets.left - insets.right;
// Determine the rows to draw within the clipped bounds.
Rectangle clip = g.getClipBounds();
int rowStartOffset = component.viewToModel(new Point(0, clip.y));
int endOffset = component.viewToModel(new Point(0, clip.y + clip.height));
while (rowStartOffset <= endOffset)
{
try
{
if (isCurrentLine(rowStartOffset))
{
g.setColor(getCurrentLineForeground());
} else
{
g.setColor(getForeground());
}
// Get the line number as a string and then determine the
// "X" and "Y" offsets for drawing the string.
String lineNumber = getTextLineNumber(rowStartOffset);
int stringWidth = fontMetrics.stringWidth(lineNumber);
int x = getOffsetX(availableWidth, stringWidth) + insets.left;
int y = getOffsetY(rowStartOffset, fontMetrics);
g.drawString(lineNumber, x, y);
// Move to the next row
rowStartOffset = Utilities.getRowEnd(component, rowStartOffset) + 1;
} catch (Exception e)
{
break;
}
}
}
/*
* We need to know if the caret is currently positioned on the line we
* are about to paint so the line number can be highlighted.
*/
private boolean isCurrentLine(int rowStartOffset)
{
int caretPosition = component.getCaretPosition();
Element root = component.getDocument().getDefaultRootElement();
if (root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition))
{
return true;
} else
{
return false;
}
}
/*
* Get the line number to be drawn. The empty string will be returned
* when a line of text has wrapped.
*/
protected String getTextLineNumber(int rowStartOffset)
{
Element root = component.getDocument().getDefaultRootElement();
int index = root.getElementIndex(rowStartOffset);
Element line = root.getElement(index);
if (line.getStartOffset() == rowStartOffset)
{
return String.valueOf(index + 1);
} else
{
return "";
}
}
/*
* Determine the X offset to properly align the line number when drawn
*/
private int getOffsetX(int availableWidth, int stringWidth)
{
return (int) ((availableWidth - stringWidth) * digitAlignment);
}
/*
* Determine the Y offset for the current row
*/
private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics)
throws BadLocationException
{
// Get the bounding rectangle of the row
Rectangle r = component.modelToView(rowStartOffset);
int lineHeight = fontMetrics.getHeight();
int y = r.y + r.height;
int descent = 0;
// The text needs to be positioned above the bottom of the bounding
// rectangle based on the descent of the font(s) contained on the row.
if (r.height == lineHeight) // default font is being used
{
descent = fontMetrics.getDescent();
} else // We need to check all the attributes for font changes
{
if (fonts == null)
{
fonts = new HashMap<>();
}
Element root = component.getDocument().getDefaultRootElement();
int index = root.getElementIndex(rowStartOffset);
Element line = root.getElement(index);
for (int i = 0; i < line.getElementCount(); i++)
{
Element child = line.getElement(i);
AttributeSet as = child.getAttributes();
String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily);
Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize);
String key = fontFamily + fontSize;
FontMetrics fm = fonts.get(key);
if (fm == null)
{
Font font = new Font(fontFamily, Font.PLAIN, fontSize);
fm = component.getFontMetrics(font);
fonts.put(key, fm);
}
descent = Math.max(descent, fm.getDescent());
}
}
return y - descent;
}
//
// Implement CaretListener interface
//
#Override
public void caretUpdate(CaretEvent e)
{
// Get the line the caret is positioned on
int caretPosition = component.getCaretPosition();
Element root = component.getDocument().getDefaultRootElement();
int currentLine = root.getElementIndex(caretPosition);
// Need to repaint so the correct line number can be highlighted
if (lastLine != currentLine)
{
repaint();
lastLine = currentLine;
}
}
//
// Implement DocumentListener interface
//
#Override
public void changedUpdate(DocumentEvent e)
{
documentChanged();
}
#Override
public void insertUpdate(DocumentEvent e)
{
documentChanged();
}
#Override
public void removeUpdate(DocumentEvent e)
{
documentChanged();
}
/*
* A document change may affect the number of displayed lines of text.
* Therefore the lines numbers will also change.
*/
private void documentChanged()
{
// View of the component has not been updated at the time
// the DocumentEvent is fired
SwingUtilities.invokeLater(() ->
{
try
{
int endPos = component.getDocument().getLength();
Rectangle rect = component.modelToView(endPos);
if (rect != null && rect.y != lastHeight)
{
setPreferredWidth();
repaint();
lastHeight = rect.y;
}
} catch (BadLocationException ex)
{ /* nothing to do */ }
});
}
//
// Implement PropertyChangeListener interface
//
#Override
public void propertyChange(PropertyChangeEvent evt)
{
if (evt.getNewValue() instanceof Font)
{
if (updateFont)
{
Font newFont = (Font) evt.getNewValue();
setFont(newFont);
lastDigits = 0;
setPreferredWidth();
} else
{
repaint();
}
}
}
}
Then line numbers can be added like this:
TextLineNumber textLineNumber = new TextLineNumber(sourceCodeArea);
sourceCodeAreaScrollPane.setRowHeaderView(textLineNumber);
I've been working on this program for a couple weeks now, adding components to an applet in order to output a simple tic tac toe game. I've gotten some of it to work, but as of now, when you run, all it does is that you click it once, the CPU projects its mark in the upper left corner, and then you can click anywhere else and it automatically says you win. I can't get the CPU to keep playing. I don't expect anyone to tell me what to do, but I'm just confused as to which method I need to work on in order to get the CPU to respond. My professor has left some very helpful pseudocode, but I still don't quite understand. I've been working with the method "gameEnd," checking for winners horizontally, vertically and diagonally to see if that's the source to getting the game to continue beyond just two marks, but it's not working. Anyone got any suggestions? Thanks.
import java.awt.Color;
import java.awt.Event;
import java.awt.Font;
import java.awt.Graphics;
import java.util.Random;
public class TicTacToe extends java.applet.Applet {
// Ignore this constant
private static final long serialVersionUID = 1942709821640345256L;
// You can change this boolean constant to control debugging log output
private static final boolean DEBUGGING = false;
// Constants
// Size of one side of the board in pixels
private static final int BOARD_SIZE_PIXELS = 600;
// Number of squares on one side of the board
private static final int SQUARES = 3;
// Diameter of the circle drawn in each square
private static final int CIRCLE_WIDTH = 90;
// Colors to be used in the game
private static final Color BACKGROUND_COLOR = Color.WHITE;
private static final Color SQUARE_BORDER_COLOR = Color.BLACK;
private static final Color GAME_OVER_MESSAGE_COLOR = Color.BLACK;
private static final Color HUMAN_COLOR = Color.RED;
private static final Color HUMAN_WINNING_COLOR = Color.MAGENTA;
private static final Color CPU_COLOR = Color.BLUE;
private static final Color CPU_WINNING_COLOR = Color.CYAN;
// Status constant values for the game board
private static final int EMPTY = 0;
private static final int HUMAN = 1;
private static final int HUMAN_WINNING = 2;
private static final int CPU = -1;
private static final int CPU_WINNING = -2;
// String displayed when the game ends
private static final String GAME_WIN_MESSAGE = "You win! Click to play again.";
private static final String GAME_LOSE_MESSAGE = "You lose......Click to play again.";
private static final String GAME_DRAW_MESSAGE = "No one wins? Click to play again...";
// Instance variables that control the game
// Whether or not the user just clicked the mouse
private boolean mouseClicked = false;
// Whether or not to start the game again
private boolean restart = false;
// Whether or not the CPU should start playing a move.
// USED ONLY WHEN THE CPU PLAYS FIRST!
private boolean onFirstMove = false;
// The column and row of the SQUARE the user clicked on
private int xMouseSquare; // column
private int yMouseSquare; // row
// The width (and height) of a single game square
private int squareWidth = BOARD_SIZE_PIXELS / SQUARES;
// An array to hold square status values on the board.
// The status values can be EMPTY, HUMAN, CPU or other values
private int[][] gameBoard;
// The column and row of the SQUARE the CPU player will move on
private int xCPUMove;
private int yCPUMove;
// Add the rest of your instance variables here, if you need any. (You won't
// need to, but you may if you want to.)
// Ignore these instance variables
// CPUinMove represents if the CPU is thinking (generating the CPU move).
// If it is true, it means the CPUMove() method is running and no new move
// should be added
private boolean CPUinMove;
// Methods that you need to write:
/*
* Pre: x and y are x-coordinate and y-coordinate where the user just
* clicks. squareWidth is the width (and height) of a single game square.
*
* Post: xMouseSquare and yMouseSquare are set to be the column and row
* where the user just clicked on (depending on x and y).
*
* Hint: You need only two statements in this method.
*/
// Setting MouseSquare equal to x and y divided by the Square Width to create a location for
// the user after clicking
private void setMouseSquare(int x, int y) {
//
xMouseSquare = x/squareWidth;
yMouseSquare = y/squareWidth;
}
/*
* Pre: SQUARES is an int that holds the number of game squares along one
* side of the game board. xSquare is an int such that 0 <= xSquare <
* SQUARES. CIRCLE_WIDTH is an int that holds the diameter of the circle to
* be drawn in the center of a square. squareWidth is an int that holds the
* width and height in pixels of a single game square.
*
* Post: Return the correct x-coordinate (in pixels) of the left side of the
* circle centered in a square in the column xSquare.
*
* Hint: This method should be very simple. What you need to do is to find
* the right equation.
*/
private int getIconDisplayXLocation(int xSquare) {
// This line is an example of using DEBUGGING variable
if (DEBUGGING) {
System.out.println("The input that getIconDisplayXLocation() receives is: " + xSquare);
}
// equation that returns the correct variable in the column xSquare
return squareWidth * xSquare + (squareWidth - CIRCLE_WIDTH)/2;
}
/*
* Pre: SQUARES is an int that holds the number of game squares along one
* side of the game board. ySquare is an int such that 0 <= ySquare <
* SQUARES. CIRCLE_WIDTH is an int that holds the diameter of the circle to
* be drawn in the center of a square. squareWidth is an int that holds the
* width and height in pixels of a single game square.
*
* Post: Return the correct y-coordinate (in pixels) of the top of the
* circle centered in a square in the row ySquare.
*
* Hint: This method should be very simple. What you need to do is to find
* the right equation.
*/
private int getIconDisplayYLocation(int ySquare) {
// This line is an example of using DEBUGGING variable
if (DEBUGGING) {
System.out.println("The input that getIconDisplayYLocation() receives is: " + ySquare);
}
// equation that returns the correct variable in the column ySquare
return squareWidth * ySquare + (squareWidth - CIRCLE_WIDTH)/2;
}
/*
* The instance variable gameBoard will be created and initialized
*
* Pre: SQUARES is set to an int. gameBoard is a 2-dimensional array type
* variable that holds the status of current game board. Each value in the
* array represents a square on the board
*
* Post: gameBoard must be assigned a new 2-dimensional array. Every square
* of gameBoard should be initialized to EMPTY.
*
* Hint: A loop.
*/
private void buildBoard() {
// Setting the two methods equal to local variables, x and y
int x = xMouseSquare;
int y = yMouseSquare;
// This line creates the gameBoard array. You must write several more
// lines to initialize all its values to EMPTY
// Write game board using the equation of three across and down for each column
// Constructs a 3 by 3 array of integers for the board
gameBoard = new int[3][3];
// Initialize variables i and j, set equal to 0; this establishes a connection for loop
for (x = 0; x < 3; x++) {
for (y = 0; y < 3; y++) {
gameBoard[x][y] = EMPTY;
}
}
}
/*
* Returns whether the most recently clicked square is a legal choice in the
* game.
*
* Pre: gameBoard is a 2-dimensional array type variable that holds the
* status of current game board. xSquare and ySquare represent the column
* and row of the most recently clicked square.
*
* Post: Returns true if and only if the square is a legal choice. If the
* square is empty on current game board, it is legal and the method shall
* return true; if it is taken by either human or CPU or it is not a square
* on current board, it is illegal and the method shall return false.
*
* Hint: Should be simple but think carefully to cover all cases.
*/
private boolean legalSquare(int xSquare, int ySquare) {
if (gameBoard[xSquare][ySquare] == EMPTY)
return true;
else return false;
}
/*
* Pre: gameBoard is an array that holds the current status of the game
* board. xSquare and ySquare represent the column and row of the most
* recently clicked square. player represent the current player (HUMAN or
* CPU). This method is always called after checking legalSquare().
*
* Post: Set the square as taken by current player on the game board if the
* square is empty.
*
* Hint: Very simple.
*/
private void setMovePosition(int xSquare, int ySquare, int player) {
player = HUMAN;
player = CPU;
this.xMouseSquare = xSquare;
this.yMouseSquare = ySquare;
}
/*
* Check if HUMAN or CPU wins the game.
*
* Pre: gameBoard is an array to hold square status values on the board. The
* status values can be EMPTY, HUMAN, CPU or other values.
*
* Post: The method will return true if and only if a player wins the game.
* Winning a game means there is at least one row, one column, or one
* diagonal that is taken by the same player. The method does not need to
* indicate who wins because if it is implemented correctly, the winner must
* be the one who just made a move.
*
* Hint: Complicated method. Use loops to shorten your code. Think about how
* to represent "a player takes 3 squares in a row/column/diagonal".
*/
private boolean gameEnd() {
// Setting local variables
int i = 0;
int x = xMouseSquare;
int y = yMouseSquare;
int sw = squareWidth;
int[][] g = gameBoard;
// Checking for a winner
// Checking columns
for (y = 0; y < 3; y++) {
for (x = 0; x < 3; x++) {
i = g[x][y] + i;
}
}
{
// Checking rows
for (x = 0; x < 3; x++) {
for (y = 0; y < 3; y++) {
i = g[x][y] + i;
}
}
}
// Checking first diagonal
{
for (x = 0; x < 3; x++) {
g[x][x] = 3;
}
}
for (x = 1; x < 3; x++) {
g[x][x] = 3;
}
for (x = 2; x < 3; x++) {
g[x][x] = 3;
}
// Checking second diagonal
for (y = 0; y < 3; y++) {
g[y][2 - y] = 3;
}
for (y = 1; y < 3; y++) {
g[y][2 - y] = 3;
}
for (y = 2; y < 3; y++) {
g[y][2 - y] = 3;
}
return true;
}
/*
* Check if the game ends as a draw.
*
* Pre: gameBoard is an array to hold square status values on the board. The
* status values can be EMPTY, HUMAN, CPU or other values. This method is
* always called after gameEnd().
*
* Post: The method will return true if and only if all squares on the game
* board are taken by HUMAN or CPU players (no EMPTY squares left).
*
* Hint: Should be simple. Use loops.
*/
// Value of...
private boolean gameDraw() {
if (squareWidth == (3^2 - 1)) {
}
return true;
}
/*
* Marks circles on the line on which a player wins.
*
* Pre: g is Graphics object that is ready to draw on. HUMAN_WINNING_COLOR
* and CPU_WINNING_COLOR are both Color objects that represent the color to
* show when HUMAN/CPU wins. gameBoard is gameBoard is an array to hold
* square status values on the board. The status values can be EMPTY, HUMAN,
* CPU or other values.
*
* Post: ALL the row(s)/column(s)/diagonal(s) on which a player wins will be
* marked as the special color.
*
* Hint: You must draw a new circle with the special color (to replace the
* old circle) on the square if the square belongs to a winning
* row/column/diagonal. You can change gameBoard because the board will be
* reset after displaying the winning line. You can use helper methods
* (existing ones or your own) to finish this method. Pay attention that
* many functions in this method is similar to gameEnd(). You should think
* about reusing code.
*
* Hint2: This method is not necessary for the game logic. You don't have to
* start it early. Start when you know everything else is correct.
*/
private void markWinningLine(Graphics g) {
// TODO
}
/*
* Generates the next square where the CPU plays a move.
*
* Pre: gameBoard is an array to hold square status values on
* the board. The status values can be EMPTY, HUMAN, CPU or other values.
*
* Post: Set xCPUMove and yCPUMove to represent the column and the row which
* CPU plans to play a move on. The respective square MUST BE EMPTY! It will
* cause a logical error if this method returns a square that is already
* taken!
*
* Hint: Don't start too early -- currently this method works (though
* naively). Make sure that everything else works before touching this
* method!
*/
private void CPUMove() {
// TODO
// The following block gives a naive solution -- it finds the first
// empty square and play a move on it. You can use this method to test
// other methods in the beginning. However, you must replace this block
// with your own algorithms eventually.
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (gameBoard[i][j] == 0) {
xCPUMove = i;
yCPUMove = j;
return;
}
}
}
}
/* Put any helper methods you wish to add here. */
/* You will not need to change anything below this line. */
/*
* DO NOT change this method.
*
* Set the game board to show a new, blank game.
*/
private void wipeBoard(Graphics g) {
g.setColor(BACKGROUND_COLOR);
g.fillRect(0, 0, BOARD_SIZE_PIXELS, BOARD_SIZE_PIXELS);
}
/*
* DO NOT change this method.
*
* Displays a circle on g, of the given color, in the center of the given
* square.
*/
private void displayHit(Graphics g, Color color, int xSquare, int ySquare) {
g.setColor(color);
g.fillOval(getIconDisplayXLocation(xSquare),
getIconDisplayYLocation(ySquare), CIRCLE_WIDTH, CIRCLE_WIDTH);
}
/*
* DO NOT change this method.
*
* This method handles mouse clicks. You will not need to call it.
*/
#Override
public boolean mouseDown(Event e, int xMouse, int yMouse) {
if (isClickable()) {
mouseClicked = true;
setMouseSquare(xMouse, yMouse);
}
repaint();
return true;
}
/*
* DO NOT change this method.
*
* This method handles drawing the board. You will not need to call it.
*/
#Override
public void update(Graphics g) {
paint(g);
}
/*
* DO NOT change this method.
*
* Draws the border between game squares onto canvas. Also, draws the moves
* that are already made.
*/
private void displayGame(Graphics canvas) {
canvas.setColor(SQUARE_BORDER_COLOR);
for (int i = 0; i < BOARD_SIZE_PIXELS; i += squareWidth) {
for (int j = 0; j < BOARD_SIZE_PIXELS; j += squareWidth) {
canvas.drawRect(i, j, squareWidth, squareWidth);
}
}
for (int i = 0; i < SQUARES; i++) {
for (int j = 0; j < SQUARES; j++) {
switch (gameBoard[i][j]) {
case HUMAN:
case HUMAN_WINNING:
displayHit(canvas, HUMAN_COLOR, i, j);
break;
case CPU:
case CPU_WINNING:
displayHit(canvas, CPU_COLOR, i, j);
break;
default:
break;
}
}
}
}
/*
* DO NOT change this method.
*
* This method relays information about the availability of mouse clicking
* in the game. You will not need to call it.
*/
private boolean isClickable() {
return !CPUinMove;
}
/*
* DO NOT change the contents this method.
*
* If this method is changed to public void paint(Graphics canvas), it will
* execute the program with the CPU-first order.
*
* This method is like the "main" method (but for applets). You will not
* need to call it. It contains most of the game logic.
*/
// #Override
public void paint(Graphics canvas) {
displayGame(canvas);
if (mouseClicked) {
if (onFirstMove) {
CPUMove();
setMovePosition(xCPUMove, yCPUMove, CPU);
displayHit(canvas, CPU_COLOR, xCPUMove, yCPUMove);
onFirstMove = false;
} else {
if (restart) {
wipeBoard(canvas);
setUpGame();
repaint();
} else if (legalSquare(xMouseSquare, yMouseSquare)) {
setMovePosition(xMouseSquare, yMouseSquare, HUMAN);
displayHit(canvas, HUMAN_COLOR, xMouseSquare, yMouseSquare);
if (gameEnd()) {
markWinningLine(canvas);
canvas.setFont(new Font("SansSerif", Font.PLAIN, 30));
canvas.setColor(GAME_OVER_MESSAGE_COLOR);
canvas.drawString(GAME_WIN_MESSAGE, squareWidth / 2,
squareWidth);
restart = true;
} else {
CPUinMove = true;
CPUMove();
setMovePosition(xCPUMove, yCPUMove, CPU);
displayHit(canvas, CPU_COLOR, xCPUMove, yCPUMove);
CPUinMove = false;
if (gameEnd()) {
markWinningLine(canvas);
canvas
.setFont(new Font("SansSerif", Font.PLAIN,
30));
canvas.setColor(GAME_OVER_MESSAGE_COLOR);
canvas.drawString(GAME_LOSE_MESSAGE,
squareWidth / 2, squareWidth);
restart = true;
} else if (gameDraw()) {
canvas
.setFont(new Font("SansSerif", Font.PLAIN,
30));
canvas.setColor(GAME_OVER_MESSAGE_COLOR);
canvas.drawString(GAME_DRAW_MESSAGE,
squareWidth / 2, squareWidth);
restart = true;
}
}
}
}
mouseClicked = false;
}
}
/*
* DO NOT change this method.
*
* This method is like the "main" method (but for applets). You will not
* need to call it. It contains most of the game logic.
*/
public void paint_game(Graphics canvas) {
// display the current game board
displayGame(canvas);
// the following block will run every time the user clicks the mouse
if (mouseClicked) { // when the user clicks the mouse
// if the game is ready to start or to be restarted
if (restart) {
// clear the window and set up the game again
wipeBoard(canvas);
setUpGame();
repaint();
}
// else, if the game is in play, check if the click is on a legal
// square
else if (legalSquare(xMouseSquare, yMouseSquare)) {
// if the square is legal, mark the corresponding position as
// taken by HUMAN
setMovePosition(xMouseSquare, yMouseSquare, HUMAN);
// display the new position with a HUMAN_COLOR circle
displayHit(canvas, HUMAN_COLOR, xMouseSquare, yMouseSquare);
// check if the game ends (if it is, HUMAN wins)
if (gameEnd()) {
// if HUMAN wins, mark the winning line as a special color.
markWinningLine(canvas);
// display the human winning message
canvas.setFont(new Font("SansSerif", Font.PLAIN, 30));
canvas.setColor(GAME_OVER_MESSAGE_COLOR);
canvas.drawString(GAME_WIN_MESSAGE, squareWidth / 2,
squareWidth);
// mark the game as ready to restart
restart = true;
} else if (gameDraw()) {
// if HUMAN doesn't win but the board is full, it is a draw
// display the draw message
canvas.setFont(new Font("SansSerif", Font.PLAIN, 30));
canvas.setColor(GAME_OVER_MESSAGE_COLOR);
canvas.drawString(GAME_DRAW_MESSAGE, squareWidth / 2,
squareWidth);
// mark the game as ready to restart
restart = true;
} else {
// if HUMAN doesn't win and the board is not full, the CPU
// is ready to move
CPUinMove = true;
// calculates the next CPU move
CPUMove();
// mark the corresponding position as taken by CPU
setMovePosition(xCPUMove, yCPUMove, CPU);
// display the new position with a CPU_COLOR circle
displayHit(canvas, CPU_COLOR, xCPUMove, yCPUMove);
CPUinMove = false;
if (gameEnd()) {
// if CPU wins, mark the winning line as a special
// color.
markWinningLine(canvas);
// display the human losing message
canvas.setFont(new Font("SansSerif", Font.PLAIN, 30));
canvas.setColor(GAME_OVER_MESSAGE_COLOR);
canvas.drawString(GAME_LOSE_MESSAGE, squareWidth / 2,
squareWidth);
// mark the game as ready to restart
restart = true;
}
// else (if the game is not ended after the CPU move), the
// game is ready to get the next HUMAN move
}
}
mouseClicked = false;
}
}
/*
* DO NOT change this method.
*
* This method initializes the applet. You will not need to call it.
*/
#Override
public void init() {
setSize(BOARD_SIZE_PIXELS, BOARD_SIZE_PIXELS);
setBackground(BACKGROUND_COLOR);
setUpGame();
}
/*
* DO NOT change this method.
*
* Creates a fresh game board and sets up the game state to get ready for a
* new game.
*/
private void setUpGame() {
buildBoard();
CPUinMove = false;
restart = false;
onFirstMove = true;
}
}
I've been given a 2^k * 2^k sized board, and one of the tiles is randomly removed making it a deficient board. The task is to fill the with "trominos" which are an L-shaped figure made of 3 tiles.
The process of the solving it isn't too difficult. If the board is 2x2, then it only takes one tromino to fill it. For any greater size, it must be divided into quarters (making four 2^(k-1) sized boards), with one tromino placed at the center point, so each quadrant has one filled in tile. After that, the board can be recursively filled until every tile is filled with a random colored tromino.
My main problem is actually implementing the code. My skills with Java programming are generally pretty weak, and I often have trouble simply finding a place to start. The only work to be done is in the tile method in the tiling class, which takes as input the deficient board to tile, the row and column to start tiling in, and the number of tiles to fill. This is a homework problem, so I'm simply looking for some guidance or a place to start - any help would be greatly appreciated.
public class BoardViewer extends JFrame {
private static final int WIDTH = 1024;
private static final int HEIGHT = 768;
private static final int OFFSET = 30;
public static final int UPVERT = 1;
public static final int DNVERT = 2;
public static final int RHORIZ = 4;
public static final int LHORIZ = 8;
public static final int FILL = 16;
private Color [][] board;
public BoardViewer(DeficientBoard db) {
super();
setSize(WIDTH + (OFFSET*2), HEIGHT + (OFFSET*2));
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
board = db.getBoardColor();
}
public void paint(Graphics g) {
super.paint(g);
int width = WIDTH/board[0].length;
int height = HEIGHT/board.length;
for (int r = 0; r < board.length; r++)
for (int c = 0; c < board[r].length; c++) {
g.setColor(board[r][c]);
int x = c*width + OFFSET;
int y = r*height + OFFSET + (OFFSET/2);
g.fillRect(x+1, y+1, width-1, height-1);
}
}
}
public class DeficientBoard {
private int n;
private Color board[][];
// The four rotations of the tile.
// UL is XX
// X
// UR is XX
// X
// LL is X
// XX
// LR is X
// XX
public final int UL = 0;
public final int UR = 1;
public final int LL = 2;
public final int LR = 3;
/**
* Create a 2^k x 2^k deficient board.
*
* #param k power
*/
public DeficientBoard(int k) {
n = (int)Math.pow(2, k);
createBoard(Color.LIGHT_GRAY);
}
/**
* Actually create an n x n deficient board.
*
* #param color background color
*/
private void createBoard(Color color) {
board = new Color[n][n];
for (int r = 0; r < board.length; r++)
for (int c = 0; c < board[0].length; c++)
board[r][c] = color;
int d_row = (int)(Math.random() * n);
int d_col = (int)(Math.random() * n);
board[d_row][d_col] = Color.BLACK;
}
/**
* Given a row and column and shape based on that point
* place a tromino of the given color.
*
* #param r row
* #param c column
* #param s shape (UL, UR, LL, LR)
* #param theColor a Color
*/
public void placeTromino(int r, int c, int s, Color theColor) {
if (s == UL) {
board[r][c] = theColor;
board[r][c+1] = theColor;
board[r+1][c] = theColor;
} else if (s == UR) {
board[r][c] = theColor;
board[r][c+1] = theColor;
board[r+1][c+1] = theColor;
} else if (s == LL) {
board[r][c] = theColor;
board[r+1][c] = theColor;
board[r+1][c+1] = theColor;
} else {
board[r+1][c] = theColor;
board[r+1][c+1] = theColor;
board[r][c+1] = theColor;
}
}
/**
* Get the 2^k x 2^k board.
*
* #return the Color board.
*/
public Color[][] getBoardColor() {
return board;
}
/**
* Find and return the deficient row.
*
* #param row row
* #param col column
* #param sz size of the baord
* #return the row the deficient block is located
*/
public int getDeficientRow(int row, int col, int sz) {
for (int r = row; r < (row + sz); r++)
for (int c = col; c < (col + sz); c++)
if (board[r][c] != Color.LIGHT_GRAY)
return r;
return -1;
}
/**
* Find and return the deficient column.
*
* #param row row
* #param col column
* #param sz size of the baord
* #return the row the deficient block is located
*/
public int getDeficientCol(int row, int col, int sz) {
for (int r = row; r < (row + sz); r++)
for (int c = col; c < (col + sz); c++)
if (board[r][c] != Color.LIGHT_GRAY)
return c;
return -1;
}
/**
* Get the size of the deficient board.
*
* #return the size
*/
public int getSize() {
return n;
}
/**
* Display information about the deficient board.
*/
public String toString() {
return ("Deficient board of size "
+ n + "x" + n
+ " with position missing at ("
+ getDeficientRow(0, 0, n) + "," + getDeficientCol(0, 0, n) +").");
}
}
public class Tiling {
private static Color randColor() {
int r = (int)(Math.random() * 256);
int g = (int)(Math.random() * 256);
int b = (int)(Math.random() * 256);
return new Color(r, g, b);
}
public static void tile(DeficientBoard db, int row, int col, int n) {
}
public static void main(String[] args) {
DeficientBoard db = new DeficientBoard(3);
System.out.println(db);
tile(db, 0, 0, db.getSize());
BoardViewer bv = new BoardViewer(db);
bv.setVisible(true);
}
}
In general, when a recursive function implements a divide-and-conquer algorithm, it has to handle two basic cases:
The base case. This is the case where you're done dividing, and need to conquer a bit. In your assignment, the base case is the case where n = 2, and in that case, you just need to find which of the four tiles is missing/painted (using DefectiveBoard.getDeficientRow and DefectiveBoard.getDeficientCol) and add the appropriate triomino to cover the other three tiles.
The recursive case. This is the case where you're not done dividing, so you need to divide (i.e., recurse), and (depending on the algorithm) may need to do a bit of conquering either before or after the recursion. In your assignment, the recursive case is the case where n > 2. In that case, you need to do two things:
Find which of the four quadrants has a missing/painted tile, and add the appropriate triomino to cover one tile from each of the other three quadrants.
Recurse, calling yourself four times (one for each quadrant).
A good starting point is to write the "Is this the base case?" check, and to implement the base case.
After that, if you don't see how to write the recursive case, one approach is to temporarily write a "one above the base" case (n = 4), and see if you can generalize it. If not, you might then temporarily write a "two above the base" case (n = 8), and so on. (Once you've got your recursive algorithm working, you would then remove these special cases, since they're fully covered by the general recursive case.)
Well this is somewhat of a harder problem to solve. However, I'd say you have the skills given how much code you wrote so I wouldn't be self conscious about it.
I don't have a complete solution formulated, but I think if you start at the the removed tile and put a trominos on either side of it. Then keep putting trominos on either side of the last trominos. You're "spooning" the tromino you last placed on the board. Once you do that to the edge of the board. All that's left is tromino shaped locations. Here is an example of what I mean (X is the dropped tile ie the gap, Y are the trominos):
_ _ _ _
|_|_|_|_|
|_|Y|Y|_|
|_|Y|X|Y|
|_|_|Y|Y|
_ _ _ _
|Y|Y|_|_|
|Y|Y|Y|_|
|_|Y|X|Y|
|_|_|Y|Y|
Once the board is filled to the edges you can essentially start dropping trominos like bombs on the rest of the board. I have a feeling there is a pattern here where you fill in the diagonal trominos while filling in the 2nd part at the same time that is repeatable. But if you can't find that then create a recursive routine that spoons the gap to the edges then transitions to adding trominos in diagonal patterns. The hint there is you have to do the transition in the first stack frame only.
Good luck.
In our application that has a JTabbedPane with unlimited tabs, when the width of the tabs exceeds the tabbed pane's width, the tabs start wrapping into several rows. When you then click on a tab in one of the upper rows, the complete row comes down and to the foreground. For users who click around between several tabs, it's highly confusing as it's not possible to keep track of the tab order.
How can I either
- nail the tabs to fixed positions while bringing their contents to the front (though this would optically corrupt the tab metaphor but I don't care), or
- limit the number of rows to one (so the tabs get very narrow instead of wrapping)?
After some hours of research I have finally found a clean solution.
First, figure out which UI class you are using. Put the following code after you initialize the L&F with UIManager.setLookAndFeel():
for (Map.Entry<Object, Object> entry : UIManager.getDefaults().entrySet()) {
boolean isStringKey = entry.getKey().getClass() == String.class ;
String key = isStringKey ? ((String) entry.getKey()):"";
if (key.equals("TabbedPaneUI")) {
System.out.println(entry.getValue());
}
}
In my case, it prints com.sun.java.swing.plaf.windows.WindowsTabbedPaneUI. If you are using a different L&F, this may be another class (or even if you are using another OS, if you take the OS default).
Next, just instantiate that class (which extends BasicTabbedPaneUI), and override the problematic method:
WindowsTabbedPaneUI jtpui = new WindowsTabbedPaneUI() {
#Override protected boolean shouldRotateTabRuns(int i) {
return false;
}
};
If eclipse does not recognize the class, and gives you an "access restriction" error if you type the full name of the class, see this question: Access restriction: The type 'Application' is not API (restriction on required library rt.jar)
Finally, just set that UI for your JTabbedPane:
JTabbedPane jtp = new JTabbedPane();
jtp.setUI(jtpui);
However, there is a problem: some L&F don't take into account the non-scrolling of tabs rows, and it turns out ugly.
To fix this (I only tested on the Windows L&F), add the following immediately after initializing the L&F:
UIManager.getDefaults().put("TabbedPane.tabRunOverlay", 0);
Really quick and dirty (definitely needs improvements and changes), but I would imagine that something like that could work for you (but is not a JTabbePane):
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class Test {
/**
* FlowLayout subclass that fully supports wrapping of components.
*/
public static class WrapLayout extends FlowLayout {
private Dimension preferredLayoutSize;
/**
* Constructs a new <code>WrapLayout</code> with a left alignment and a
* default 5-unit horizontal and vertical gap.
*/
public WrapLayout() {
super();
}
/**
* Constructs a new <code>FlowLayout</code> with the specified alignment
* and a default 5-unit horizontal and vertical gap. The value of the
* alignment argument must be one of <code>WrapLayout</code>,
* <code>WrapLayout</code>, or <code>WrapLayout</code>.
*
* #param align
* the alignment value
*/
public WrapLayout(int align) {
super(align);
}
/**
* Creates a new flow layout manager with the indicated alignment and
* the indicated horizontal and vertical gaps.
* <p>
* The value of the alignment argument must be one of
* <code>WrapLayout</code>, <code>WrapLayout</code>, or
* <code>WrapLayout</code>.
*
* #param align
* the alignment value
* #param hgap
* the horizontal gap between components
* #param vgap
* the vertical gap between components
*/
public WrapLayout(int align, int hgap, int vgap) {
super(align, hgap, vgap);
}
/**
* Returns the preferred dimensions for this layout given the
* <i>visible</i> components in the specified target container.
*
* #param target
* the component which needs to be laid out
* #return the preferred dimensions to lay out the subcomponents of the
* specified container
*/
#Override
public Dimension preferredLayoutSize(Container target) {
return layoutSize(target, true);
}
/**
* Returns the minimum dimensions needed to layout the <i>visible</i>
* components contained in the specified target container.
*
* #param target
* the component which needs to be laid out
* #return the minimum dimensions to lay out the subcomponents of the
* specified container
*/
#Override
public Dimension minimumLayoutSize(Container target) {
Dimension minimum = layoutSize(target, false);
minimum.width -= getHgap() + 1;
return minimum;
}
/**
* Returns the minimum or preferred dimension needed to layout the
* target container.
*
* #param target
* target to get layout size for
* #param preferred
* should preferred size be calculated
* #return the dimension to layout the target container
*/
private Dimension layoutSize(Container target, boolean preferred) {
synchronized (target.getTreeLock()) {
// Each row must fit with the width allocated to the containter.
// When the container width = 0, the preferred width of the
// container
// has not yet been calculated so lets ask for the maximum.
int targetWidth = target.getSize().width;
if (targetWidth == 0) {
targetWidth = Integer.MAX_VALUE;
}
int hgap = getHgap();
int vgap = getVgap();
Insets insets = target.getInsets();
int horizontalInsetsAndGap = insets.left + insets.right + hgap * 2;
int maxWidth = targetWidth - horizontalInsetsAndGap;
// Fit components into the allowed width
Dimension dim = new Dimension(0, 0);
int rowWidth = 0;
int rowHeight = 0;
int nmembers = target.getComponentCount();
for (int i = 0; i < nmembers; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
// Can't add the component to current row. Start a new
// row.
if (rowWidth + d.width > maxWidth) {
addRow(dim, rowWidth, rowHeight);
rowWidth = 0;
rowHeight = 0;
}
// Add a horizontal gap for all components after the
// first
if (rowWidth != 0) {
rowWidth += hgap;
}
rowWidth += d.width;
rowHeight = Math.max(rowHeight, d.height);
}
}
addRow(dim, rowWidth, rowHeight);
dim.width += horizontalInsetsAndGap;
dim.height += insets.top + insets.bottom + vgap * 2;
// When using a scroll pane or the DecoratedLookAndFeel we need
// to
// make sure the preferred size is less than the size of the
// target containter so shrinking the container size works
// correctly. Removing the horizontal gap is an easy way to do
// this.
Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);
if (scrollPane != null) {
dim.width -= hgap + 1;
}
return dim;
}
}
/*
* A new row has been completed. Use the dimensions of this row
* to update the preferred size for the container.
*
* #param dim update the width and height when appropriate
* #param rowWidth the width of the row to add
* #param rowHeight the height of the row to add
*/
private void addRow(Dimension dim, int rowWidth, int rowHeight) {
dim.width = Math.max(dim.width, rowWidth);
if (dim.height > 0) {
dim.height += getVgap();
}
dim.height += rowHeight;
}
}
public static class MyTabbedPane extends JPanel {
private JPanel buttonPanel;
private JPanel currentview;
private Tab currentTab;
private class Tab {
String name;
JComponent component;
}
private List<Tab> tabs = new ArrayList<Tab>();
public MyTabbedPane() {
super(new BorderLayout());
buttonPanel = new JPanel(new WrapLayout());
currentview = new JPanel();
add(buttonPanel, BorderLayout.NORTH);
add(currentview);
}
public void addTab(String name, JComponent tabView, int index) {
if (index < 0 || index > tabs.size()) {
throw new IllegalArgumentException("Index out of bounds");
}
final Tab tab = new Tab();
tab.component = tabView;
tab.name = name;
tabs.add(index, tab);
JButton b = new JButton(name);
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
setCurrentTab(tab);
}
});
buttonPanel.add(b, index);
buttonPanel.validate();
}
public void removeTab(int i) {
Tab tab = tabs.remove(i);
if (tab == currentTab) {
if (tabs.size() > 0) {
if (i < tabs.size()) {
setCurrentTab(tabs.get(i));
} else {
setCurrentTab(tabs.get(i - 1));
}
} else {
setCurrentTab(null);
}
}
buttonPanel.remove(index);
}
void setCurrentTab(final Tab tab) {
if (currentTab == tab) {
return;
}
if (currentTab != null) {
currentview.remove(currentTab.component);
}
if (tab != null) {
currentview.add(tab.component);
}
currentTab = tab;
currentview.validate();
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
MyTabbedPane tabbedPane = new MyTabbedPane();
for (int i = 0; i < 100; i++) {
tabbedPane.addTab("Button " + (i + 1), new JLabel("Dummy Label " + (i + 1)), i);
}
frame.add(tabbedPane);
frame.pack();
frame.setSize(new Dimension(1000, 800));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
The WrapLayout was taken from another post on SO.