I would like to draw an 8 by 8 chess board dynamically in android studio using the canvas and by overriding method onDraw, I almost have succeeded but I am running into a very annoying problem.
I already made a custom view, my goal is to make the parent layout match the size of it's child custom view, so my chessboard can cover the entire screen. ( in XML file both parent and child are set to match_parent)
I want the chess board to cover the full height of the screen as well.
Check the picture below here's the result I am getting, notice that huge gap in the lower area, my purpose is to stretch the chessboard vertically and make it cover the entire screen to eliminate that space.
Here's what I managed to write so far but it's not doing what I want :
Class ChessBoard:
public class ChessBoard extends View {
private static final String TAG = ChessBoard.class.getSimpleName();
private Rect rect;
private static final int COLS = 8;
private static final int ROWS = 8;
Tile tile;
private final Tile[][] mTiles;
private int x0 = 0;
private int y0 = 0;
private static final int DEF_SQUARE_SIZE=50;
private int squareSize=0;
private boolean flipped = false;
public ChessBoard(final Context context,AttributeSet attrs) {
super(context,attrs);
this.mTiles = new Tile[COLS][ROWS];
buildTiles();
rect=new Rect();
}
onDraw method:
protected void onDraw(final Canvas canvas)
{
int width = getWidth();
int height = getHeight();
squareSize=Math.min(getSquareSizeWidth(width),getSquareSizeHeight(height));
computeOrigins(width,height);
for (int c = 0; c < COLS; c++) {
for (int r = 0; r < ROWS; r++) {
final int xCoord = getXCoord(c);
final int yCoord = getYCoord(r);
mTiles[c][r].setTileRect(rect);
rect.left=xCoord;
rect.top=yCoord;
rect.right= rect.left+squareSize; // right
rect.bottom=rect.top + squareSize;
mTiles[c][r].draw(canvas);
}
}
Class Tile :
public final class Tile {
private static final String TAG = Tile.class.getSimpleName();
private final int col;
private final int row;
private final Paint squareColor;
private Rect tileRect;
public Tile(final int col, final int row) {
this.col = col;
this.row = row;
this.squareColor = new Paint();
squareColor.setColor(isDark() ? Color.BLACK : Color.WHITE);
squareColor.setAntiAlias(true);
}
public void draw(final Canvas canvas) {
canvas.drawRect(tileRect, squareColor);
}
public String getColumnString() {
switch (col) {
case 0: return "A";
case 1: return "B";
case 2: return "C";
case 3: return "D";
case 4: return "E";
case 5: return "F";
case 6: return "G";
case 7: return "H";
default: return null;
}
}
public String getRowString() {
// To get the actual row, add 1 since 'row' is 0 indexed.
return String.valueOf(row + 1);
}
public void handleTouch() {
Log.d(TAG, "handleTouch(): col: " + col);
Log.d(TAG, "handleTouch(): row: " + row);
}
public boolean isDark() {
return (col + row) % 2 == 0;
}
public boolean isTouched(final int x, final int y) {
return tileRect.contains(x, y);
}
public void setTileRect(final Rect tileRect) {
this.tileRect = tileRect;
}
public String toString() {
final String column = getColumnString();
final String row = getRowString();
return "<Tile " + column + row + ">";
}
}
onMeasure Mehtod:
protected void onMeasure(int widthMeasureSpec, int
heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
int sqSizeW = getSquareSizeWidth(width);
int sqSizeH = getSquareSizeHeight(height);
int sqSize = Math.min(sqSizeW, sqSizeH);
if (height > width) {
int p = getMaxHeightPercentage();
height = Math.min(getHeight(sqSize), height * p / 100);
} else {
width = Math.min(getWidth(sqSize), width * 65 / 100);
}
setMeasuredDimension(width, height);
}
Class ChessBoard methods:
protected int getWidth(int sqSize) {
return sqSize * 8;
}
protected int getHeight(int sqSize) {
return sqSize * 8;
}
private int getSquareSizeWidth(final int width) {
return (width)/ 8;
}
private int getSquareSizeHeight(final int height) {
return (height)/8;
}
private int getXCoord(final int x) {
return x0 + squareSize * (flipped ? 7 - x : x);
}
private int getYCoord(final int y) {
return y0 + squareSize * (flipped ? y : 7 - y);
}
private void computeOrigins(final int width, final int height) {
this.x0 = (width - squareSize *8)/2 ;
this.y0 = (height - squareSize *8)/2;
}
protected int getMaxHeightPercentage() {
return 75;
}
}
Kindly note that the onDraw method above is implemented in the Chess Board Class.
I am sure the solution for this problem lies in the onMeasure Method, I am thinking if I increase the height of each square then the chessboard will stretch horizontally and cover the whole screen ? Can anyone please tell me what I am doing wrong ?
Well, not quite sure what you want to achieve by having a non-square chessboard which is not how usually it is represented. In any case, just looking at your code somehow following lines looks a bit suspicious:
squareSize=Math.min(getSquareSizeWidth(width),getSquareSizeHeight(height));
and
rect.right= rect.left+squareSize; // right
rect.bottom=rect.top + squareSize;
So, your tile rect always seems to be setting to same width and height, which is minimum of width and height as per these lines.
Related
Currently my sprite animates like it was in movement not only when is in movement but also when it stays in one place. Of course I want to stay still without animation when it stays in one place. How to solve that?
public abstract class GameMovingObject {
private static final int ROW_TOP_TO_BOTTOM = 0;
private static final int ROW_RIGHT_TO_LEFT = 1;
private static final int ROW_LEFT_TO_RIGHT = 2;
private static final int ROW_BOTTOM_TO_TOP = 3;
public boolean justSeen=true;
protected Bitmap image;
private final int rowCount, colCount;
protected final int WIDTH, HEIGHT;
private final int width, height;
private int x;
public int getX() { return this.x; }
public void setX(int x) { this.x = x; }
private int y;
public int getY() { return this.y; }
public void setY(int y) { this.y = y; }
// Row index of Image are being used.
private int rowUsing = ROW_LEFT_TO_RIGHT;
private int colUsing;
private Bitmap[] leftToRights;
private Bitmap[] rightToLefts;
private Bitmap[] topToBottoms;
private Bitmap[] bottomToTops;
// Velocity of game character (pixel/millisecond)
public float velocity = 0.15f;
public int getMovingVectorX() {
return movingVectorX;
}
public int getMovingVectorY() {
return movingVectorY;
}
public int movingVectorX = 0;
public int movingVectorY = 0;
public long lastDrawNanoTime =-1;
public GameSurface gs;
public GameMovingObject(GameSurface gs, Bitmap image, int rowCount, int colCount, int x, int y) {
this.gs = gs;
this.image = image;
this.rowCount = rowCount;
this.colCount = colCount;
this.x = x;
this.y = y;
this.WIDTH = image.getWidth();
this.HEIGHT = image.getHeight();
this.width = this.WIDTH / colCount;
this.height = this.HEIGHT / rowCount;
this.topToBottoms = new Bitmap[colCount]; // 3
this.rightToLefts = new Bitmap[colCount]; // 3
this.leftToRights = new Bitmap[colCount]; // 3
this.bottomToTops = new Bitmap[colCount]; // 3
for(int col = 0; col< this.colCount; col++ ) {
this.topToBottoms[col] = this.createSubImageAt(ROW_TOP_TO_BOTTOM, col);
this.rightToLefts[col] = this.createSubImageAt(ROW_RIGHT_TO_LEFT, col);
this.leftToRights[col] = this.createSubImageAt(ROW_LEFT_TO_RIGHT, col);
this.bottomToTops[col] = this.createSubImageAt(ROW_BOTTOM_TO_TOP, col);
}
}
public Bitmap[] getMoveBitmaps() {
switch (rowUsing) {
case ROW_BOTTOM_TO_TOP:
return this.bottomToTops;
case ROW_LEFT_TO_RIGHT:
return this.leftToRights;
case ROW_RIGHT_TO_LEFT:
return this.rightToLefts;
case ROW_TOP_TO_BOTTOM:
return this.topToBottoms;
default:
return null;
}
}
public void setMovingVector(int movingVectorX, int movingVectorY) {
this.movingVectorX= movingVectorX;
this.movingVectorY = movingVectorY;
}
public Bitmap getCurrentMoveBitmap() {
Bitmap[] bitmaps = this.getMoveBitmaps();
return bitmaps[this.colUsing];
}
public void draw(Canvas canvas) {
Bitmap bitmap = this.getCurrentMoveBitmap();
canvas.drawBitmap(bitmap,x, y, null);
// Last draw time.
this.lastDrawNanoTime= System.nanoTime();
}
public void update() {
this.colUsing++;
if(colUsing >= this.colCount) {
this.colUsing =0;
}
// Current time in nanoseconds
long now = System.nanoTime();
// Never once did draw.
if(lastDrawNanoTime==-1) {
lastDrawNanoTime= now;
}
// Change nanoseconds to milliseconds (1 nanosecond = 1000000 milliseconds).
int deltaTime = (int) ((now - lastDrawNanoTime)/ 777777 );
// Distance moves
float distance = velocity * deltaTime;
double movingVectorLength = Math.sqrt(movingVectorX* movingVectorX + movingVectorY*movingVectorY);
// Calculate the new position of the game character.
this.x = x + (int)(distance* movingVectorX / movingVectorLength);
this.y = y + (int)(distance* movingVectorY / movingVectorLength);
// When the game's character touches the edge of the screen, then change direction
if(this.x < 0 ) {
this.x = 0;
this.movingVectorX = - this.movingVectorX;
} else if(this.x > this.gs.getWidth() -width) {
this.x= this.gs.getWidth()-width;
this.movingVectorX = - this.movingVectorX;
}
if(this.y < 0 ) {
this.y = 0;
this.movingVectorY = - this.movingVectorY;
}
// rowUsing (obraca postać)
if( movingVectorX > 0 ){
if(movingVectorY > 0 && Math.abs(movingVectorX) < Math.abs(movingVectorY)) {
this.rowUsing = ROW_TOP_TO_BOTTOM;
}
else if(movingVectorY < 0 && Math.abs(movingVectorX) < Math.abs(movingVectorY)) {
this.rowUsing = ROW_BOTTOM_TO_TOP;
}
else {
this.rowUsing = ROW_LEFT_TO_RIGHT;
}
}
else
{
if(movingVectorY > 0 && Math.abs(movingVectorX) < Math.abs(movingVectorY)) {
this.rowUsing = ROW_TOP_TO_BOTTOM;
}
else if(movingVectorY < 0 && Math.abs(movingVectorX) < Math.abs(movingVectorY)) {
this.rowUsing = ROW_BOTTOM_TO_TOP;
}
else if(movingVectorX!=0 || movingVectorY!=0) {
this.rowUsing = ROW_RIGHT_TO_LEFT;
}
}
}
protected Bitmap createSubImageAt(int row, int col) {
// createBitmap(bitmap, x, y, width, height).
Bitmap subImage = Bitmap.createBitmap(image, col * width, row * height, width, height);
return subImage;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
I was trying to add
if(movingVectorY!=0) this.topToBottoms[col] = this.createSubImageAt(ROW_TOP_TO_BOTTOM, col);
else this.topToBottoms[1] = this.createSubImageAt(ROW_TOP_TO_BOTTOM, 1);
but then sprite blinks and is without animation even when moving. I think I added everything you need to know. It there is anything you want me to add just ask me. Thank you in advance.
I'm trying to make a random map generator. It should create a random sized rooms at random coordinates, and remove the room it it overlaps with other rooms. However, the overlap checking isn't working. Here are the relevant parts of code:
public static void generateMap() {
rooms[0] = new Room(0,10,10,5); // For some reason doesn't work without this?
for (int i=0;i<ROOMS;i++) {
int x = randomWithRange(0,WIDTH);
int y = randomWithRange(0,HEIGHT);
int height = randomWithRange(MINROOMSIZE,MAXROOMSIZE);
int width = randomWithRange(MINROOMSIZE,MAXROOMSIZE);
while (x+width > WIDTH) {
x--;
}
while (y+height > HEIGHT) {
y--;
}
Room room = new Room(x,y,width,height);
if (room.overlaps(rooms) == false) {
rooms[i] = room;
}
}
}
And then the Room class:
import java.awt.*;
public class Room {
int x;
int y;
int height;
int width;
public Room(int rx, int ry, int rwidth, int rheight) {
x = rx;
y = ry;
height = rheight;
width = rwidth;
}
boolean overlaps(Room[] roomlist) {
boolean overlap = true;
Rectangle r1 = new Rectangle(x,y,width,height);
if (roomlist != null) {
for (int i=0;i<roomlist.length;i++) {
if (roomlist[i] != null) {
Rectangle r2 = new Rectangle(roomlist[i].x,roomlist[i].y,roomlist[i].width,roomlist[i].height);
if (!r2.intersects(r1) && !r1.intersects(r2)) {
overlap = false;
}
else {
overlap = true;
}
}
}
}
return overlap;
}
}
So I've been testing this, and it removes a few rooms each time, but there's always some that are overlapping depending on the number of rooms of course. There must be some stupid easy solution I just can't see right now... Also, why doesn't it generate any rooms unless I manually add the first one? Thanks
Your problem is this part of overlaps function:
overlap = false;
What is happening in your code is that you keep checking rooms if they overlap or not, but if you find one which overlaps, you keep going. And then when you find a room which does not overlap, you reset the flag. Effectively the code is equivalent with just checking the last room.
Remove the overlap flag completely. Instead of overlap = true; statement put return true; (because at this point we know that at least one room is overlapping). Don't do anything when you find out that the room is not overlapping with other room (in the for cycle). At the end, after the for cycle just return false; Fact that code execution got to that point means there is no overlapping room (otherwise it would have just returned already)
Note: I believe that condition !r2.intersects(r1) && !r1.intersects(r2) is redundant. .intersects(r) should be commutative, meaning that that r1.intersects(r2) and r2.intersects(r1) give the same results.
For your first issue where you have initialized first room, you don't have to do that.
rooms[0] = new Room(0,10,10,5); // For some reason doesn't work without this?
You just need to check for first room and no need to check for overlap as it is the first room.
For the second issue, you can return true at the first time you find an intersect otherwise return false at the end of the loop.
Code for your reference.
class Room {
int x;
int y;
int height;
int width;
public Room(int rx, int ry, int rwidth, int rheight) {
x = rx;
y = ry;
height = rheight;
width = rwidth;
}
boolean overlaps(Room[] roomlist) {
Rectangle r1 = new Rectangle(x, y, width, height);
if (roomlist != null) {
for (int i = 0; i < roomlist.length; i++) {
if (roomlist[i] != null) {
Rectangle r2 = new Rectangle(roomlist[i].x, roomlist[i].y, roomlist[i].width, roomlist[i].height);
if (r2.intersects(r1)) {
return true;
}
}
}
}
return false;
}
}
public class RoomGenerator {
private static final int ROOMS = 10;
private static final int WIDTH = 1200;
private static final int HEIGHT = 1000;
private static final int MINROOMSIZE = 10;
private static final int MAXROOMSIZE = 120;
public static void main(String[] args) {
generateMap();
}
public static void generateMap() {
Room rooms[] = new Room[10];
for (int i = 0; i < ROOMS; i++) {
int x = randomWithRange(0, WIDTH);
int y = randomWithRange(0, HEIGHT);
int height = randomWithRange(MINROOMSIZE, MAXROOMSIZE);
int width = randomWithRange(MINROOMSIZE, MAXROOMSIZE);
while (x + width > WIDTH) {
x--;
}
while (y + height > HEIGHT) {
y--;
}
Room room = new Room(x, y, width, height);
if( i ==0)
{
rooms[0] = room;
}else if (room.overlaps(rooms) == false) {
rooms[i] = room;
}
}
}
private static int randomWithRange(int min, int max) {
// TODO Auto-generated method stub
Random r = new Random();
return r.nextInt((max - min) + 1) + min;
}
}
I have a problem with a hexagonal grid. I found this code you can see below on Internet, so it's not mine. There are two public classes: hexgame which generates the grid and hexmech which draws and fills every single hexagon. What I'd like to do is basically insert an image into a specific hexagon, but I don't know how to code this and in which part of the classes I should put it. Am I thinking the wrong way?
Thank you very much for your help!
Hexgame
package hex;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class hexgame
{
private hexgame() {
initGame();
createAndShowGUI();
}
final static Color COLOURBACK = Color.WHITE;
final static Color COLOURCELL = Color.WHITE;
final static Color COLOURGRID = Color.BLACK;
final static Color COLOURONE = new Color(255,255,255,200);
final static Color COLOURONETXT = Color.BLUE;
final static Color COLOURTWO = new Color(0,0,0,200);
final static Color COLOURTWOTXT = new Color(255,100,255);
final static Color COLOURSAFE = Color.WHITE;
final static Color COLOURDANGEROUS = Color.LIGHT_GRAY;
final static int EMPTY = 0;
final static int UNKNOWN = -1;
final static int SAFE = 1;
final static int DANGEROUS = 2;
final static int CLICKED = 3;
final static int COLUMN_SIZE = 23;
final static int ROW_SIZE = 14;
final static int HEXSIZE = 45;
final static int BORDERS = 15;
int[][] board = new int[COLUMN_SIZE][ROW_SIZE];
void initGame(){
hexmech.setXYasVertex(false);
hexmech.setHeight(HEXSIZE);
hexmech.setBorders(BORDERS);
for (int i=0;i<COLUMN_SIZE;i++) {
for (int j=0;j<ROW_SIZE;j++) {
board[i][j]=EMPTY;
}
}
board[5][5] = SAFE;
board[5][6] = SAFE;
board[5][7] = SAFE;
board[6][5] = SAFE;
board [6][6] = SAFE;
board[4][4] = UNKNOWN;
}
private void createAndShowGUI()
{
DrawingPanel panel = new DrawingPanel();
JFrame frame = new JFrame("Hex Testing 4");
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
Container content = frame.getContentPane();
content.add(panel);
frame.setSize(825, 630);
frame.setResizable(true);
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
class DrawingPanel extends JPanel
{
public DrawingPanel()
{
setBackground(COLOURBACK);
MyMouseListener ml = new MyMouseListener();
addMouseListener(ml);
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setFont(new Font("TimesRoman", Font.PLAIN, 15));
super.paintComponent(g2);
for (int i=0;i<COLUMN_SIZE;i++) {
for (int j=0;j<ROW_SIZE;j++) {
if (board[i][j] != UNKNOWN)
hexmech.drawHex(i,j,g2);
}
}
for (int i=0;i<COLUMN_SIZE;i++) {
for (int j=0;j<ROW_SIZE;j++) {
if (board[i][j] != UNKNOWN)
hexmech.fillHex(i,j,board[i][j],g2);
}
}
}
class MyMouseListener extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
int x = e.getX();
int y = e.getY();
Point p = new Point( hexmech.pxtoHex(e.getX(),e.getY()) );
if (p.x < 0 || p.y < 0 || p.x >= COLUMN_SIZE || p.y >= ROW_SIZE) return;
board[p.x][p.y] = CLICKED;
repaint();
}
}
}
}
Hexmech
package hex;
import java.awt.*;
import javax.swing.*;
public class hexmech
{
#define HEXEAST 0
#define HEXSOUTHEAST 1
#define HEXSOUTHWEST 2
#define HEXWEST 3
#define HEXNORTHWEST 4
#define HEXNORTHEAST 5
public final static boolean orFLAT= true;
public final static boolean orPOINT= false;
public static boolean ORIENT= orFLAT;
public static boolean XYVertex=true;
private static int BORDERS=50
private static int s=0; // length of one side
private static int t=0; // short side of 30o triangle outside of each hex
private static int r=0; // radius of inscribed circle (centre to middle of each side). r= h/2
private static int h=0; // height. Distance between centres of two adjacent hexes. Distance between two opposite sides in a hex.
public static void setXYasVertex(boolean b) {
XYVertex=b;
}
public static void setBorders(int b){
BORDERS=b;
}
public static void setSide(int side) {
s=side;
t = (int) (s / 2); //t = s sin(30) = (int) CalculateH(s);
r = (int) (s * 0.8660254037844);
h=2*r;
}
public static void setHeight(int height) {
h = height;
r = h/2; // r = radius of inscribed circle
s = (int) (h / 1.73205); // s = (h/2)/cos(30)= (h/2) / (sqrt(3)/2) = h / sqrt(3)
t = (int) (r / 1.73205); // t = (h/2) tan30 = (h/2) 1/sqrt(3) = h / (2 sqrt(3)) = r / sqrt(3)
}
public static Polygon hex (int x0, int y0) {
int y = y0 + BORDERS;
int x = x0 + BORDERS;
if (s == 0 || h == 0) {
System.out.println("ERROR: size of hex has not been set");
return new Polygon();
}
int[] cx,cy;
if (XYVertex)
cx = new int[] {x,x+s,x+s+t,x+s,x,x-t}; //this is for the top left vertex being at x,y. Which means that some of the hex is cutoff.
else
cx = new int[] {x+t,x+s+t,x+s+t+t,x+s+t,x+t,x}; //this is for the whole hexagon to be below and to the right of this point
cy = new int[] {y,y,y+r,y+r+r,y+r+r,y+r};
return new Polygon(cx,cy,6);
}
public static void drawHex(int i, int j, Graphics2D g2) {
int x = i * (s+t);
int y = j * h + (i%2) * h/2;
Polygon poly = hex(x,y);
g2.setColor(hexgame.COLOURCELL);
//g2.fillPolygon(hexmech.hex(x,y));
g2.fillPolygon(poly);
g2.setColor(hexgame.COLOURGRID);
g2.drawString(String.format("%c;%d", 'A'+i, j+1), x+20, y+40);
g2.drawPolygon(poly);
}
public static void fillHex(int i, int j, int n, Graphics2D g2) {
char c='o';
int x = i * (s+t);
int y = j * h + (i%2) * h/2;
/*if (n < 0) {
g2.setColor(hexgame.COLOURONE);
g2.fillPolygon(hex(x,y));
g2.setColor(hexgame.COLOURONETXT);
c = (char)(-n);
g2.drawString(""+c, x+r+BORDERS, y+r+BORDERS+4); //FIXME: handle XYVertex
//g2.drawString(x+","+y, x+r+BORDERS, y+r+BORDERS+4);
}
if (n > 0) {
g2.setColor(hexgame.COLOURTWO);
g2.fillPolygon(hex(x,y));
g2.setColor(hexgame.COLOURTWOTXT);
c = (char)n;
if (n==3) {
g2.setColor(hexgame.COLOURTWO);
g2.fillPolygon(hex(x,y));
g2.setColor(hexgame.COLOURTWOTXT);
}
}
public static Point pxtoHex(int mx, int my) {
Point p = new Point(-1,-1);
//correction for BORDERS and XYVertex
mx -= BORDERS;
my -= BORDERS;
if (XYVertex) mx += t;
int x = (int) (mx / (s+t));
int y = (int) ((my - (x%2)*r)/h);
int dx = mx - x*(s+t);
int dy = my - y*h;
if (my - (x%2)*r < 0) return p; // prevent clicking in the open halfhexes at the top of the screen
//System.out.println("dx=" + dx + " dy=" + dy + " > " + dx*r/t + " <");
//even columns
if (x%2==0) {
if (dy > r) { //bottom half of hexes
if (dx * r /t < dy - r) {
x--;
}
}
if (dy < r) { //top half of hexes
if ((t - dx)*r/t > dy ) {
x--;
y--;
}
}
} else { // odd columns
if (dy > h) { //bottom half of hexes
if (dx * r/t < dy - h) {
x--;
y++;
}
}
if (dy < h) { //top half of hexes
//System.out.println("" + (t- dx)*r/t + " " + (dy - r));
if ((t - dx)*r/t > dy - r) {
x--;
}
}
}
p.x=x;
p.y=y;
return p;
}
In your implementation of paintComponent(), invoke setClip() with a suitable Shape, such as Polygon. You can size and translate the Polygon to match the destination hexagon using the createTransformedShape() method of AffineTransform. Use the coordinates of the polygon's boundary as the basis for the coordinates used in your call to drawImage(). A related example using Ellipse2D is shown here.
How to Rotate Circle with Text on TouchEvent or on TrackBallMoveEvent.
How do I create this kind of circle?
I had created a circle and rotated it also, but it always starts from 0 degrees.
Is there any other option to create this kind of circle?
Each circle have different text and each of the circles can move independently.
So, this is definitely not complete, but I think it's most of what you need.
Limitations/Assumptions
I have so far only implemented touch handling, as I think that's more difficult. If I get time later, I'll come back and add trackball handling.
I did not give the spinning discs any momentum. After the user's finger leaves the disc, it stops spinning.
I'm not sure the focus transitions between discs are 100% right. You'll have to do some testing. They're mostly right, at least.
When you mentioned Canvas in the title, I assumed that didn't mean you required this to utilize the J2ME Canvas. Writing BlackBerry apps with the RIM UI libraries is pretty much all I've done.
Solution
Essentially, I created a Field subclass to represent each disc. You create the field by passing in an array of labels, to be spaced around the perimeter, a radius, and a color. Hardcoded in each DiscField is an edge inset for the text, which kind of assumes a certain size difference between discs. You should probably make that more dynamic.
public class DiscField extends Field {
/** Used to map Manager's TouchEvents into our coordinate system */
private int _offset = 0;
private int _radius;
private int _fillColor;
private double _currentRotation = 0.0;
private double _lastTouchAngle = 0.0;
private boolean _rotating = false;
private String[] _labels;
/** Text inset from outer disc edge */
private static final int INSET = 30;
private DiscField() {
}
public DiscField(String[] labels, int radius, int fillColor) {
super(Field.FOCUSABLE);
_labels = labels;
_radius = radius;
_fillColor = fillColor;
}
protected void layout(int width, int height) {
setExtent(Math.min(width, getPreferredWidth()), Math.min(height, getPreferredHeight()));
}
private void drawFilledCircle(Graphics g, int x, int y, int r) {
// http://stackoverflow.com/a/1186851/119114
g.fillEllipse(x, y, x + r, y, x, y + r, 0, 360);
}
private void drawCircle(Graphics g, int x, int y, int r) {
g.drawEllipse(x, y, x + r, y, x, y + r, 0, 360);
}
protected void paint(Graphics graphics) {
int oldColor = graphics.getColor();
graphics.setColor(_fillColor);
drawFilledCircle(graphics, _radius, _radius, _radius);
graphics.setColor(Color.WHITE);
drawCircle(graphics, _radius, _radius, _radius);
// plot the text around the circle, inset by some 'padding' value
int textColor = (_fillColor == Color.WHITE) ? Color.BLACK : Color.WHITE;
graphics.setColor(textColor);
// equally space the labels around the disc
double interval = (2.0 * Math.PI / _labels.length);
for (int i = 0; i < _labels.length; i++) {
// account for font size when plotting text
int fontOffsetX = getFont().getAdvance(_labels[i]) / 2;
int fontOffsetY = getFont().getHeight() / 2;
int x = _radius + (int) ((_radius - INSET) * Math.cos(i * interval - _currentRotation)) - fontOffsetX;
int y = _radius - (int) ((_radius - INSET) * Math.sin(i * interval - _currentRotation)) - fontOffsetY;
graphics.drawText(_labels[i], x, y);
}
graphics.setColor(oldColor);
}
protected void drawFocus(Graphics graphics, boolean on) {
if (on) {
int oldColor = graphics.getColor();
int oldAlpha = graphics.getGlobalAlpha();
// just draw a white shine to indicate focus
graphics.setColor(Color.WHITE);
graphics.setGlobalAlpha(80);
drawFilledCircle(graphics, _radius, _radius, _radius);
// reset graphics context
graphics.setColor(oldColor);
graphics.setGlobalAlpha(oldAlpha);
}
}
protected void onUnfocus() {
super.onUnfocus();
_rotating = false;
}
protected boolean touchEvent(TouchEvent event) {
switch (event.getEvent()) {
case TouchEvent.MOVE: {
setFocus();
// Get the touch location, within this Field
int x = event.getX(1) - _offset - _radius;
int y = event.getY(1) - _offset - _radius;
if (x * x + y * y <= _radius * _radius) {
double angle = MathUtilities.atan2(y, x);
if (_rotating) {
// _lastTouchAngle only valid if _rotating
_currentRotation += angle - _lastTouchAngle;
// force a redraw (paint) with the new rotation angle
invalidate();
} else {
_rotating = true;
}
_lastTouchAngle = angle;
return true;
}
}
case TouchEvent.UNCLICK:
case TouchEvent.UP: {
_rotating = false;
return true;
}
case TouchEvent.DOWN: {
setFocus();
int x = event.getX(1) - _offset - _radius;
int y = event.getY(1) - _offset - _radius;
if (x * x + y * y <= _radius * _radius) {
_lastTouchAngle = MathUtilities.atan2(y, x);
_rotating = true;
return true;
}
}
default:
break;
}
return super.touchEvent(event);
}
protected boolean trackwheelRoll(int arg0, int arg1, int arg2) {
return super.trackwheelRoll(arg0, arg1, arg2);
// TODO!
}
public int getPreferredHeight() {
return getPreferredWidth();
}
public int getPreferredWidth() {
return 2 * _radius;
}
public String[] getLabels() {
return _labels;
}
public void setLabels(String[] labels) {
this._labels = labels;
}
public int getRadius() {
return _radius;
}
public void setRadius(int radius) {
this._radius = radius;
}
public double getCurrentAngle() {
return _currentRotation;
}
public void setCurrentAngle(double angle) {
this._currentRotation = angle;
}
public int getOffset() {
return _offset;
}
public void setOffset(int offset) {
this._offset = offset;
}
}
Containing all the DiscField objects is the DiscManager. It aligns the child DiscFields in sublayout(), and handles proper delegation of touch events ... since the fields overlap, and a touch within a DiscFields extent that does not also fall within its radius (i.e. the corners) should be handled by a larger disc.
/**
* A DiscManager is a container for DiscFields and manages proper delegation
* of touch event handling.
*/
private class DiscManager extends Manager {
private int _maxRadius = 0;
public DiscManager(long style){
super(style);
DiscField outerDisc = new DiscField(new String[] { "1", "2", "3", "4", "5", "6" },
180, Color.BLUE);
_maxRadius = outerDisc.getRadius();
DiscField middleDisc = new DiscField(new String[] { "1", "2", "3", "4", "5" },
120, Color.GRAY);
middleDisc.setOffset(_maxRadius - middleDisc.getRadius());
DiscField innerDisc = new DiscField(new String[] { "1", "2", "3", "4" },
60, Color.RED);
innerDisc.setOffset(_maxRadius - innerDisc.getRadius());
// order matters here:
add(outerDisc);
add(middleDisc);
add(innerDisc);
}
protected void sublayout(int width, int height) {
setExtent(2 * _maxRadius, 2 * _maxRadius);
// each disc needs to have the same x,y center to be concentric
for (int i = 0; i < getFieldCount(); i++) {
if (getField(i) instanceof DiscField) {
DiscField disc = (DiscField) getField(i);
int xCenter = _maxRadius - disc.getRadius();
int yCenter = _maxRadius - disc.getRadius();
setPositionChild(disc, xCenter, yCenter);
layoutChild(disc, 2 * _maxRadius, 2 * _maxRadius);
}
}
}
protected boolean touchEvent(TouchEvent event) {
int eventCode = event.getEvent();
// Get the touch location, within this Manager
int x = event.getX(1);
int y = event.getY(1);
if ((x >= 0) && (y >= 0) && (x < getWidth()) && (y < getHeight())) {
int field = getFieldAtLocation(x, y);
if (field >= 0) {
DiscField df = null;
for (int i = 0; i < getFieldCount(); i++) {
if (getField(field) instanceof DiscField) {
int r = ((DiscField)getField(field)).getRadius();
// (_maxRadius, _maxRadius) is the center of all discs
if ((x - _maxRadius) * (x - _maxRadius) + (y - _maxRadius) * (y - _maxRadius) <= r * r) {
df = (DiscField)getField(field);
} else {
// touch was not within this disc's radius, so the one slightly bigger
// should be passed this touch event
break;
}
}
}
// Let event propagate to child field
return (df != null) ? df.touchEvent(event) : super.touchEvent(event);
} else {
if (eventCode == TouchEvent.DOWN) {
setFocus();
}
// Consume the event
return true;
}
}
// Event wasn't for us, let superclass handle in default manner
return super.touchEvent(event);
}
}
Finally, a screen to use them:
public class DiscScreen extends MainScreen {
public DiscScreen() {
super(MainScreen.VERTICAL_SCROLL | MainScreen.VERTICAL_SCROLLBAR);
add(new DiscManager(Field.USE_ALL_WIDTH));
}
}
Results
I recently took all my code a manually imported it into an eclipse project from BlueJ, I then got use to the settings up "Run Configurations", finally thinking I was home free. Then I ran the code, and I got this error
java.lang.NoSuchMethodError: main
Exception in thread "main"
so I figured I had to add a main method (I never had to do this in BlueJ, why?). So I did that just called the constructor method (in BlueJ I would just create a new object and the JFrame would show). So I did that, same error. After trying different things (such as moving the code in the constructor to a different method etc.). I just put this in for the main method:
public void main(String[] args)
{
System.out.println("Hello, this is main why won't Java find this.");
}
After that I still got the same error, so I then decided to add it to all my classes to make sure it wasn't using another class as the main class. Still same error, so I come to you wondering if any of you have encountered this problem. Also I did search Google and all I found was problems with private classes etc and sense all my classes are public (hey I come from Python :) ). I knew that wasn't the problem. Help Please :)
Picture of my Run Configuration
This is my main method code
WARNING: LONG
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
public class AppFrame extends JFrame
{
public String status = "Status:";// Status of Applet
public int paint_count = 1;// Number of times applet has been painted
public int[] mousePos = {-1, -1};// Stores Mouse's Last Clicked X and Y Cordinates
public int[] boardPos = {-1, -1};//Stores The Board's X and Y Cordinates
public int[] selectedSquarePos = {-1, -1};
public int[] boardSquare = {-1, -1};//Stores Last Clicked Square
public Color P1_Color = Color.GRAY;
public Color P2_Color = Color.WHITE;
public Color SquareEven = Color.BLACK;
public Color SquareOdd = Color.RED;// pieces move on this one
public int boardHeight = 400;
public int boardWidth = 400;
public boolean pieceSelected = false;
public boolean CheckersPiece = false;
public Board CheckersBoard = new Board();
public Image buffer = null;
public Graphics bg = null;
public void main(String[] args)
{
System.out.println("Hello, this is main why won't Java find this.");
}
public AppFrame()
{
super("JCheckers");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(900,900);
setVisible(true);
buffer = createImage(getWidth(), getHeight());
boardHeight = getHeight() - 40; // 20 pixel border at top and bottom and 20 pixels for blue bar
boardWidth = getWidth() - 40; // 20 pixel border at left and right
bg = buffer.getGraphics();
addMouseListener(new MouseAdapter()
{
public void mouseClicked (MouseEvent e)
{
handleClick(e);
}
}
);
}
public void handleClick(MouseEvent e)
{
/* Handles tracking of mouse clicks; DOES NOT HANDLE DISPLAY, it just updates the data and calls redraw */
mousePos[0] = e.getX();
mousePos[1] = e.getY();
repaint();
}
public void paint(Graphics g)
{
super.paint(g);
render(g);
}
public void render(Graphics g)
{
bg.clearRect(0,0, getWidth(), getHeight());
//Draw Chess Board and Pieces
renderChessBoard(bg, 20, 20);
// Display Info
System.out.println(status);
System.out.println(String.format("Last Mouse Click # (X:%d Y:%d)", mousePos[0], mousePos[1]) );
System.out.println("Paint #" + paint_count );
System.out.println(String.format("Board Square (x:%s, y:%s) %b", boardSquare[0], boardSquare[1], CheckersPiece) );
System.out.println(CheckersBoard.status );
paint_count += 1;
// Draw Image to Screen
g.drawImage(buffer, 0, 25, null);// so it does not get hidden by the blue close/min/max room
}
public boolean isValidSquare(int col, int row)
{
if (col > -1 & col < 8) {return true;}
return false;
}
public void renderChessBoard(Graphics g, int x, int y)
{
/* Renders board and pieces*/
// sense the row squares are well squares then the
// board will be a square also; so we draw it with whatever
// side is smallest, width or height
boardPos[0] = x;
boardPos[1] = y;
drawBoard(g, x, y, boardWidth, boardHeight);
boardSquare = getBoardSquare(mousePos[0], mousePos[1]);
CheckersPiece = isCheckersPiece(boardSquare[0], boardSquare[1]);
boolean validSquare = isValidSquare(boardSquare[0], boardSquare[1]);
if (validSquare)
{
if (CheckersPiece)
{
selectSquare(g, boardSquare[0], boardSquare[1]);
}
else
{
if (pieceSelected)
{
int selectedCol = selectedSquarePos[0];
int selectedRow = selectedSquarePos[1];
int toCol = boardSquare[0];
int toRow = boardSquare[1];
System.out.println(selectedCol + " " + selectedRow + " " + toCol + " " + toRow);
if (!CheckersBoard.move(selectedSquarePos, boardSquare)) // not a valid move;
{
pieceSelected = false;
}
}
}
}
parseBoard(CheckersBoard.board, g);
}
public void drawBoard(Graphics g, int Bx, int By, int Bw, int Bh)
{
int numberRowsDrawed = 0;
int rH = Bh / 8;
int rW = Bw; // Row width is the same as the Board's width because the board and the row share the same sides
while (numberRowsDrawed < 8)
{
int rY = (numberRowsDrawed * rH) + By;
// Row X is the same as the Board X because the board and the row share the same sides
int rX = Bx;
Color EVEN = SquareEven;
Color ODD = SquareOdd;
// Yes Yes The EVEN Color is now odd and vica versa its because rows only now there row counts and so they start at 0 and don't
// include the rows above
if ((numberRowsDrawed % 2) != 0) {EVEN = SquareOdd; ODD = SquareEven;}
drawRow(g, rX, rY, rW, rH, EVEN, ODD);
numberRowsDrawed +=1;
}
}
public void drawRow(Graphics g, int x, int y, int width, int height, Color EVEN, Color ODD)
{
System.out.println("Board Y: " + y);
int squareW = width / 8;
int squareH = height;
int numberSquaresCreated = 0;
while (numberSquaresCreated < 8)
{
// needs a special case because Java's modulo uses division (so it would give a divide by 0 error) STUPID JAVA!!!!!!
if (numberSquaresCreated == 0)
{
g.setColor(EVEN);
g.fillRect(x, y, squareW, squareH);
}
else
{
if (numberSquaresCreated % 2 == 0){g.setColor(EVEN);}
else {g.setColor(ODD);}
int sX = x + (squareW * numberSquaresCreated);
g.fillRect(sX, y, squareW, squareH);
}
numberSquaresCreated +=1;
}
}
public void drawMan(Graphics g, int boardRow, int boardCol, Color pieceColor)
{
int x = boardPos[0];
int y = boardPos[1];
int pixelPosX = x + ((boardWidth / 8) * boardRow);
int pixelPosY = y + ((boardHeight / 8) * boardCol);
g.setColor(pieceColor);
g.fillOval(pixelPosX + 13, pixelPosY + 13, (boardWidth / 8) - 26, (boardHeight / 8) - 26);
}
public void drawKing(Graphics g, int boardRow, int boardCol, Color pieceColor, Color crownColor)
{
drawMan(g, boardRow, boardCol, pieceColor);
g.setColor(crownColor);
int x = boardPos[0];
int y = boardPos[1];
double DsizeFactor = ( (float) boardHeight / 8.0) / 3.75;
int sizeFactor = (int) DsizeFactor;
int pixelPosX = x + ((boardWidth / 8) - sizeFactor) / 2 + ((boardWidth / 8) * boardRow);
int pixelPosY = y + ((boardHeight / 8) - sizeFactor) / 2 + ((boardHeight / 8) * boardCol);
int[] xPoints = {pixelPosX, pixelPosX, pixelPosX + sizeFactor, pixelPosX + sizeFactor, pixelPosX + ((sizeFactor * 3) / 4), pixelPosX + (sizeFactor / 2), pixelPosX + (sizeFactor / 4) };
int[] yPoints = {pixelPosY, pixelPosY + sizeFactor, pixelPosY + sizeFactor, pixelPosY, pixelPosY + (sizeFactor / 2), pixelPosY, pixelPosY + (sizeFactor / 2)};
g.fillPolygon(xPoints, yPoints, 7);
}
public void selectSquare(Graphics g, int bSX, int bSY)
{
g.setColor(Color.YELLOW);
/*+10 is to offset text (the xy cordinates are the bottom left side of the text NOT top left.*/
pieceSelected = true;
int squareX = boardPos[0] + (boardWidth / 8) * bSX;
int squareY = 10 + boardPos[1] + (boardHeight / 8) * bSY;
selectedSquarePos[0] = bSX;
selectedSquarePos[1] = bSY;
g.drawString("Selected", squareX, squareY);
}
// Data Handling and Retreiving methods
public void parseBoard(String[][] Board, Graphics g)
{
int row = 0;
int col = 0;
for (String[] rowOfPieces : Board)
{
for (String piece : rowOfPieces)
{
if (piece != "X")
{
Color PIECE_COLOR = P1_Color;
if (piece.contains("P2")) {PIECE_COLOR = P2_Color;}
if (piece.contains("C"))
{
drawMan(g, col, row, PIECE_COLOR);
}
if (piece.contains("K"))
{
Color Crown_Color = P2_Color;
if (PIECE_COLOR != P1_Color) {Crown_Color = P1_Color;}
drawKing(g, col, row, PIECE_COLOR, Crown_Color);
}
}
col+=1;
}
row +=1;
col = 0;
}
}
public int[] getBoardSquare(int x, int y)
{
//row or col = boardpos - offset / row height or width
if ((x < boardPos[0]) | (y < boardPos[1]) | (x > (boardPos[0] + boardWidth)) | (y > (boardPos[1] + boardHeight)) )
{
int[] BS = {-1, -1};
return BS;
}
int col = (x - boardPos[0]) / (boardWidth / 8);
int row = (y - boardPos[1]) / (boardHeight / 8);
int[] BS = {col, row};
return BS;
}
public boolean isCheckersPiece(int BoardSquareX, int BoardSquareY)
{
int Px = BoardSquareX;
int Py = BoardSquareY;
if (Px == -1 & Py == -1)
{
return false;
}
String Square = CheckersBoard.board[Py][Px];
return Square != "X";
}
}
You forgot static:
public static void main(String[] args)
But in order to really start your application, you should launch it from that method, not merely have it. Here is how to start it:
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
AppFrame frame = new AppFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
});
}
It's important to note the EXIT_ON_CLOSE - this will make the JVM exit when you press the X button. Otherwise your application will continue running, and you might even not notice.
Make main static.