Created
October 29, 2014 15:10
-
-
Save ClickerMonkey/b0c0f41ad13a1c0a0d34 to your computer and use it in GitHub Desktop.
A shape & color matching game for kids.
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
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