Skip to content

Instantly share code, notes, and snippets.

@ClickerMonkey
Created October 29, 2014 15:10
Show Gist options
  • Save ClickerMonkey/b0c0f41ad13a1c0a0d34 to your computer and use it in GitHub Desktop.
Save ClickerMonkey/b0c0f41ad13a1c0a0d34 to your computer and use it in GitHub Desktop.
A shape & color matching game for kids.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.event.KeyEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import com.gameprogblog.engine.Play;
import com.gameprogblog.engine.PlayLoop;
import com.gameprogblog.engine.PlayLoopVariable;
import com.gameprogblog.engine.PlayScreen;
import com.gameprogblog.engine.PlayState;
import com.gameprogblog.engine.ObjectUtil;
import com.gameprogblog.engine.Scene;
import com.gameprogblog.engine.Vector;
import com.gameprogblog.engine.input.PlayInput;
public class Matching implements Play
{
public static void main( String[] args )
{
Play game = new Matching();
PlayLoop loop = new PlayLoopVariable( 0.1f );
PlayScreen screen = new PlayScreen( 640, 480, Color.black, true, loop, game );
PlayScreen.showWindow( screen, "Matching" );
}
Random random = new Random();
boolean playing;
List<MatchingPiece> pieceList;
List<MatchingPiece> holeList;
MatchingPiece dragging;
Vector draggingOffset = new Vector();
Vector mouse = new Vector();
Color highlightColor = Color.white;
List<Color> availableColors = Arrays.asList( Color.red/*, Color.blue, Color.yellow, Color.green, Color.orange, Color.pink*/ );
List<MatchingShape> availableShapes = Arrays.asList( MatchingShape.STAR, MatchingShape.DIAMOND, MatchingShape.HEART, MatchingShape.CIRCLE, MatchingShape.SQUARE );
List<Float> availableScales = Arrays.asList( 1.0f );
@Override
public void start( Scene scene )
{
playing = true;
build( scene, 5, 20, availableColors, availableShapes, availableScales );
}
private void build( Scene scene, int pieceCount, int padding, List<Color> availableColors, List<MatchingShape> availableShapes, List<Float> availableScales )
{
pieceList = new ArrayList<MatchingPiece>();
holeList = new ArrayList<MatchingPiece>();
while (pieceList.size() < pieceCount)
{
MatchingPiece piece = new MatchingPiece();
piece.state = MatchingState.DRAGGABLE;
piece.color = random( availableColors );
piece.shape = random( availableShapes );
piece.scale = random( availableScales );
if (!pieceList.contains( piece ))
{
pieceList.add( piece );
MatchingPiece hole = new MatchingPiece();
hole.state = MatchingState.HOLE;
hole.color = piece.color;
hole.shape = piece.shape;
hole.scale = piece.scale;
holeList.add( hole );
}
}
boolean vertical = (scene.height > scene.width);
int height = Math.max( scene.width, scene.height );
int width = Math.min( scene.width, scene.height );
int availableHeight = height - (padding * 2);
int availableWidth = width - (padding * 2);
int baseSize = Math.min( (availableHeight - padding * (pieceCount - 1)) / pieceCount, availableWidth / 3 );
float halfSize = baseSize / 2.0f;
int spacing = (availableHeight - baseSize) / (pieceCount - 1);
int[] offset = new int[pieceCount];
for (int i = 0; i < pieceCount; i++)
{
offset[i] = i;
}
for (int i = 0; i < pieceCount; i++)
{
int k = random.nextInt( pieceCount );
int j = offset[i];
offset[i] = offset[k];
offset[k] = j;
}
for (int i = 0; i < pieceCount; i++)
{
MatchingPiece p = pieceList.get( i );
MatchingPiece h = holeList.get( i );
p.size = baseSize;
h.size = baseSize;
if (vertical)
{
h.position.x = scene.width - (p.position.x = padding + halfSize);
h.position.y = offset[i] * spacing + padding + halfSize;
p.position.y = i * spacing + padding + halfSize;
}
else
{
h.position.y = scene.height - (p.position.y = padding + halfSize);
h.position.x = offset[i] * spacing + padding + halfSize;
p.position.x = i * spacing + padding + halfSize;
}
}
}
private <T> T random( List<T> objects )
{
return objects.get( random.nextInt( objects.size() ) );
}
@Override
public void input( PlayInput input )
{
if (input.keyDown[KeyEvent.VK_ESCAPE])
{
playing = false;
}
mouse.set( input.mouseX, input.mouseY );
if (input.mouseDragging)
{
if (dragging == null)
{
for (MatchingPiece p : pieceList)
{
if (p.state == MatchingState.DRAGGABLE)
{
if (p.distance( input.mouseX, input.mouseY ) <= p.size * p.scale * 0.5f)
{
dragging = p;
}
}
}
}
else
{
highlightColor = Color.white;
for (MatchingPiece h : holeList)
{
if (h.distance( input.mouseX, input.mouseY ) <= h.size * h.scale * 0.5f)
{
if (dragging.shape == h.shape && dragging.color == h.color && dragging.scale == h.scale)
{
highlightColor = dragging.color;
}
else
{
highlightColor = new Color( 255 - dragging.color.getRed(), 255 - dragging.color.getGreen(), 255 - dragging.color.getBlue() );
}
}
}
}
}
else if (dragging != null)
{
for (MatchingPiece h : holeList)
{
if (h.distance( input.mouseX, input.mouseY ) <= h.size * h.scale * 0.5f)
{
if (dragging.shape == h.shape && dragging.color == h.color && dragging.scale == h.scale)
{
dragging.state = MatchingState.FIXED;
dragging.position.set( h.position );
}
}
}
dragging = null;
}
}
@Override
public void update( PlayState state, Scene scene )
{
boolean reset = true;
for (MatchingPiece p : pieceList)
{
if (p.state == MatchingState.DRAGGABLE)
{
reset = false;
}
}
if (reset)
{
build( scene, pieceList.size(), 20, availableColors, availableShapes, availableScales );
}
}
@Override
public void draw( PlayState state, Graphics2D gr, Scene scene )
{
if (dragging != null)
{
LINE.setLine( mouse.x, mouse.y, dragging.position.x, dragging.position.y );
Stroke currentStroke = gr.getStroke();
gr.setColor( highlightColor );
gr.setStroke( new BasicStroke( 40.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND ) );
gr.draw( LINE );
gr.setStroke( currentStroke );
draw( gr, dragging, highlightColor, 20 );
gr.setColor( dragging.color );
gr.setStroke( new BasicStroke( 20.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND ) );
gr.draw( LINE );
gr.setStroke( currentStroke );
}
for (MatchingPiece h : holeList)
{
draw( gr, h, null, 20 );
draw( gr, h, Color.darkGray, 5 );
}
for (MatchingPiece p : pieceList)
{
draw( gr, p, null, 0 );
}
}
private void draw( Graphics2D gr, MatchingPiece p, Color overrideColor, float sizeIncrease )
{
draw( gr, p.position.x, p.position.y, p.size * p.scale + sizeIncrease, overrideColor != null ? overrideColor : p.color, p.shape );
}
static Ellipse2D.Float ELLIPSE = new Ellipse2D.Float();
static Rectangle2D.Float RECTANGLE = new Rectangle2D.Float();
static Path2D.Float PATH = new Path2D.Float();
static Line2D.Float LINE = new Line2D.Float();
private void draw( Graphics2D gr, float x, float y, float s, Color color, MatchingShape shape )
{
gr.setColor( color );
float hs = s * 0.5f;
switch (shape)
{
case CIRCLE:
ELLIPSE.setFrame( x - hs, y - hs, s, s );
gr.fill( ELLIPSE );
break;
case DIAMOND:
PATH.reset();
PATH.moveTo( x, y - hs );
PATH.lineTo( x + hs, y );
PATH.lineTo( x, y + hs );
PATH.lineTo( x - hs, y );
PATH.closePath();
gr.fill( PATH );
break;
case SQUARE:
RECTANGLE.setFrame( x - hs, y - hs, s, s );
gr.fill( RECTANGLE );
break;
case TRIANGLE:
PATH.reset();
PATH.moveTo( x, y - hs );
PATH.lineTo( x + hs, y + hs );
PATH.lineTo( x - hs, y + hs );
PATH.closePath();
gr.fill( PATH );
case STAR:
PATH.reset();
final float thirtySixDegrees = 0.62831853f;
float theta;
for (int i = 0; i < 10; i++)
{
theta = (float)Math.PI * 3.0f / 2.0f + thirtySixDegrees * i;
float starX = (float)(x + Math.cos( theta ) * hs / (i % 2 + 1));
float starY = (float)(y + Math.sin( theta ) * hs / (i % 2 + 1));
if (i == 0)
{
PATH.moveTo( starX, starY );
}
else
{
PATH.lineTo( starX, starY );
}
}
PATH.closePath();
gr.fill( PATH );
break;
case HEART:
float w = (float)Math.sqrt(hs * hs / 8.0f); // From center of hump to X or Y coordinate on edge of hump
PATH.reset();
PATH.moveTo( x - hs / 2, y - hs / 2 );
PATH.lineTo( x + hs / 2, y - hs / 2 );
PATH.lineTo( x + hs / 2 + w, y - hs / 2 + w );
PATH.lineTo( x, y + hs / 2 + 2 * w - hs / 2 );
PATH.lineTo( x - hs / 2 - w, y - hs / 2 + w );
gr.fill( PATH );
ELLIPSE.setFrame( x - hs, y - hs, hs, hs );
gr.fill( ELLIPSE );
ELLIPSE.setFrame( x, y - hs, hs, hs );
gr.fill( ELLIPSE );
break;
}
}
@Override
public void destroy()
{
}
@Override
public boolean isPlaying()
{
return playing;
}
public enum MatchingShape
{
CIRCLE, TRIANGLE, SQUARE, DIAMOND, STAR, HEART
}
public enum MatchingState
{
DRAGGABLE, HOLE, FIXED
}
public class MatchingPiece
{
MatchingState state;
MatchingShape shape;
Color color;
Vector position = new Vector();
float scale;
float size;
public float distance( float x, float y )
{
float dx = x - position.x;
float dy = y - position.y;
return (float)Math.sqrt( dx * dx + dy * dy );
}
public int hashCode()
{
return ObjectUtil.hashCode( shape, color, scale );
}
public boolean equals( Object obj )
{
if (!ObjectUtil.isEquatable( this, obj ))
{
return false;
}
MatchingPiece other = (MatchingPiece)obj;
return ObjectUtil.equals( shape, other.shape ) &&
ObjectUtil.equals( color, other.color ) &&
ObjectUtil.equals( scale, other.scale );
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment