Created
May 13, 2012 05:43
-
-
Save yifanlu/2685104 to your computer and use it in GitHub Desktop.
Java programming guide by example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This purpose of this Tetris demo is to demostrate three things: | |
1) Structure of a game | |
We have a game loop, a clock, an update method, and a draw method | |
*) The clock calculates the number of seconds since the last loop. | |
This allows us to move the game at a constant pace even if the CPU fluctuates. | |
*) The update method does the logic of the game, including collision checks, random character generation, etc. | |
The update method delegates the task, so each object is responsible for updating itself | |
*) The draw method does the rendering of stuff to screen, and just like the update method, each object draws itself | |
This structure allows for easy debugging, modularization for easy extension, and allow delegation of tasks to different people for groups | |
2) Object-Orientated programming | |
We follow the calling convention: noun.verb() in where we make the noun (object) the most important aspect | |
We follow proper visibility in where each object can only modify its own instance variables | |
Class closure allows one object to only see itself and what it "owns". no public/public static variables | |
3) Readability, which is important for collaborating | |
Classes and variables have clear, english, names descriptive of what they do | |
Method names follow a convention (doesn't have to be this): verb() for voids, getNoun() for getters, isNoun() for booleans, etc all camel case | |
Braces are consistant throughout code (here, egyptian style is used), for methods, classes, and flows | |
Comments that don't describe what the code does (redundent), but serves as headers for finding "where" a major task happens and justification for weird code | |
Instance variables are clearly labeled so it doesn't get confusing when reading the code | |
Proper abstraction allows tasks to be delegated to others | |
No "magic numbers", enums and constants are used in lieu of a number in middle of code | |
Constructors do little more than setting instance variables and calling other constructors (abstract to methods do the heavy work) | |
Null instance variables are expressed explicitly. No suprises later. | |
This in no way should be regarded as absolute fact or with any authority at all. It's just the stuff I've leared here and there through reading millions of lines of major open source projects. | |
~Yifan Lu | |
*/ | |
class TetrisGame { | |
private int mDeltaTime; | |
private Grid mGrid; | |
public void tick(){ | |
// set deltaTime to number of miliseconds since last call | |
} | |
public void run(){ | |
mGrid = new Grid(); | |
while(true){ | |
tick(); | |
update(); | |
draw(); | |
} | |
} | |
public void keyEvent(Key k){ | |
mGrid.keyEvent(k); | |
} | |
public void update(){ | |
mGrid.update(mDeltaTime); | |
} | |
public void draw(){ | |
// draw hud and stuff here | |
mGrid.draw(); | |
} | |
} | |
class Grid { | |
public enum Direction { | |
UP, DOWN, LEFT, RIGHT; | |
} | |
private Piece mCurrentPiece; | |
private Piece mNextPiece; // to show on side of screen | |
private Piece mHoldPiece; | |
private Set<Block> mBlocks; | |
private int mTimeSinceDrop; | |
private int mTimeLimit; | |
private long mScore; | |
public static final int ROWS = 12, COLUMNS = 24; | |
public Grid(){ | |
mCurrentPiece = generatePiece(); | |
mNextPiece = generatePiece(); | |
mHoldPiece = null; | |
mBlocks = new TreeSet<Block>; | |
mScore = 0; | |
} | |
public boolean canMove(Piece p, Direction d){ | |
// returns if the piece can move in direction d | |
} | |
public Piece generatePiece(){ | |
// set type to a random type | |
return new Piece(type); | |
} | |
public void keyEvent(Key k){ | |
switch(k){ | |
case Key.Up: mCurrentPiece.moveUp(); break; | |
case Key.Down: mCurrentPiece.moveDown(); break; | |
case Key.Left: mCurrentPiece.moveLeft(); break; | |
case Key.Right: mCurrentPiece.moveRight(); break; | |
case Key.R: | |
case Key.RCTRL: | |
case Key.Space: | |
mCurrentPiece.rotateRight(); break; | |
case Key.L: | |
case Key.LCTRL: | |
mCurrentPiece.rotateLeft(); break; | |
case Key.D: | |
mCurrentPiece.drop(); break; | |
case Key.S: | |
Piece temp = mCurrentPiece; | |
mCurrentPiece = mNextPiece; | |
mNextPiece = temp; | |
mCurrentPiece.setLocation(temp.getLocation()); | |
break; | |
} | |
} | |
public void update(int deltaTime){ | |
// first see if block should drop | |
mTimeSinceDrop += deltaTime | |
while(mTimeSinceDrop - mTimeLimit > 0){ | |
mTimeSinceDrop -= mTimeLimit; | |
mCurrentPiece.moveDown(); | |
// TODO: Check if piece is frozen and do stuff | |
} | |
// freeze blocks | |
if(mCurrentPiece.isFrozen()){ | |
for(Block b : mCurrentPiece.getBlocks()){ | |
mBlocks.add(b); | |
} | |
mCurrentPiece = mNextPiece; | |
mNextPiece = generatePiece(); | |
} | |
// here, deal with clearing rows that are filled and updating score | |
// here, deal with level ups, change time limit based on level | |
// update blocks | |
for(Block b : mBlocks){ | |
b.update(deltaTime); | |
} | |
} | |
public void draw(){ | |
// draw grid and stuff code here | |
// draw current piece | |
mCurrentPiece.draw(); | |
// draw blocks | |
for(Block b : mBlocks){ | |
b.draw(); | |
} | |
} | |
} | |
class Piece { | |
public enum Type { | |
I, J, L, O, S, T, Z; | |
} | |
private static Piece I_PIECE; | |
private static Piece J_PIECE; | |
private static Piece K_PIECE; | |
private static Piece O_PIECE; | |
private static Piece S_PIECE; | |
private static Piece T_PIECE; | |
private static Piece Z_PIECE; | |
static { | |
// create default pieces here | |
} | |
private Set<Block> mBlocks; // immutable, no setter | |
private Grid mInGrid; // immutable, no setter | |
private boolean mFrozen; | |
public Piece(Type t, Grid g){ | |
switch(t){ | |
case I: this(I_PIECE, Grid g); break; | |
case J: this(J_PIECE, Grid g); break; | |
case L: this(L_PIECE, Grid g); break; | |
case O: this(O_PIECE, Grid g); break; | |
case S: this(S_PIECE, Grid g); break; | |
case T: this(T_PIECE, Grid g); break; | |
case Z: this(Z_PIECE, Grid g); break; | |
} | |
} | |
public Piece(Piece copy, Grid g){ | |
this(copy, new Location(0, 0), Grid g); | |
} | |
public Piece(Piece copy, Location loc, Grid g){ | |
this.mBlocks = new TreeSet<Block>(); | |
for(Block b : copy.mBlocks){ | |
Location bLocation = new Location(loc); | |
bLocation.setX(bLocation.getX() + b.getLocation().getX()); | |
bLocation.setX(bLocation.getY() + b.getLocation().getY()); | |
this.mBlocks.add(new Block(bLocation, new Color(b.getColor()))); | |
} | |
this.mInGrid = g; | |
} | |
// create moveUp, moveDown, moveLeft, moveRight. bounds checking: mInGrid.canMove(). mFrozen = true if cannot move | |
// create rotateLeft, rotateRight. bounds checking: mInGrid.canMove(). kick-backs can be done with if(!rotate) try moveRight, rotate then moveLeft, rotate else cannot rotate | |
public void drop(){ | |
while(!mFrozen){ | |
moveDown(); | |
} | |
} | |
public boolean isFrozen(){ | |
return mFrozen; | |
} | |
public void freeze(){ | |
mFrozen = true; | |
} | |
public Set<Block> getBlocks(){ | |
return mBlocks; | |
} | |
public void update(int deltaTime){ | |
// update blocks | |
for(Block b : mBlocks){ | |
b.update(deltaTime); | |
} | |
} | |
public void draw(){ | |
// draw blocks | |
for(Block b : mBlocks){ | |
b.draw(); | |
} | |
} | |
} | |
class Block { | |
private Location mLocation; | |
private Color mColor; | |
public Block(Location loc, Color col){ | |
this.mLocation = loc; | |
this.mColor = col; | |
} | |
// getter and setter for mLocation, mColor | |
public void update(int deltaTime){ | |
// nothing here, could be extended in future | |
} | |
public void draw(){ | |
// draw based on location and color | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment