Skip to content

Instantly share code, notes, and snippets.

@yifanlu
Created May 13, 2012 05:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yifanlu/2685104 to your computer and use it in GitHub Desktop.
Save yifanlu/2685104 to your computer and use it in GitHub Desktop.
Java programming guide by example
/*
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