Created
January 8, 2013 01:19
-
-
Save JohnEarnest/4480242 to your computer and use it in GitHub Desktop.
A prototype game exploring the idea of a bullet hell space-shooter in which the player takes an entirely defensive role and must protect others in addition to themselves.
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
/** | |
* Stop | |
* | |
* A prototype game exploring the idea of a | |
* bullet hell space-shooter in which the | |
* player takes an entirely defensive role | |
* and must protect others in addition to | |
* themselves. | |
* | |
* Known issues/ things to ponder: | |
* | |
* - What is your motivation for NOT having the shield up at all times? | |
* - make it run out of juice while taking hits/ recharge while retracted? | |
* - make the player more mobile somehow when not shielding? | |
* - tried keeping shield angle fixed while deployed- testers didn't like it. | |
* - Why/How does the player empathize with the things they are saving? | |
* | |
* TODO: | |
* | |
* - enlarge wave counter for constrast with timer | |
* - periodically spawn more people to defend | |
* - indicate oncoming enemies? | |
* | |
**/ | |
import java.util.*; | |
import java.util.List; // awt is dumb. | |
import java.awt.*; | |
import java.awt.geom.*; | |
import java.awt.event.*; | |
import java.awt.image.*; | |
import javax.swing.*; | |
// just for the screen capture feature: | |
import javax.imageio.*; | |
import java.io.*; | |
public class Stop extends JPanel implements KeyListener { | |
/** | |
* Rendering engine bits and utilities: | |
**/ | |
private static final int WIDTH = 160; | |
private static final int HEIGHT = 120; | |
private static final int GOAL_FPS = 50; | |
static final BufferedImage buff = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB); | |
public static void main(String[] args) { | |
JFrame window = new JFrame("Stop"); | |
Stop app = new Stop(); | |
app.setPreferredSize(new Dimension(320*3, 240*3)); | |
window.setCursor(window.getToolkit().createCustomCursor( | |
new BufferedImage(1,1, BufferedImage.TYPE_INT_ARGB), | |
new Point(0, 0), | |
"invisible" | |
)); | |
window.addKeyListener(app); | |
window.add(app); | |
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | |
window.setResizable(false); | |
window.pack(); | |
window.setVisible(true); | |
app.newgame(); | |
while(true) { | |
long start = System.nanoTime(); | |
synchronized(buff) { app.tick(); } | |
app.repaint(); | |
long total = System.nanoTime() - start; | |
if ((total / 1000000) < (1000 / GOAL_FPS)) { | |
try { Thread.sleep((1000 / GOAL_FPS) - (total / 1000000)); } | |
catch(InterruptedException e) {} | |
} | |
} | |
} | |
public void keyTyped(KeyEvent e) {} | |
public void keyPressed(KeyEvent e) {} | |
private int screenshot = 0; | |
public void keyReleased(KeyEvent e) { | |
if (e.getKeyCode() == KeyEvent.VK_S) { | |
synchronized(buff) { | |
try { ImageIO.write(buff, "PNG", new File("shot"+(screenshot++)+".png")); } | |
catch(IOException ioe) {} | |
} | |
} | |
} | |
public void paint(Graphics g) { | |
synchronized(buff) { | |
draw((Graphics2D)buff.getGraphics()); | |
g.drawImage( | |
buff, | |
0, 0, getWidth(), getHeight(), | |
0, 0, WIDTH, HEIGHT, | |
this | |
); | |
} | |
} | |
void outline(Graphics2D g, Shape s) { | |
g.setColor(Color.WHITE); | |
g.fill(s); | |
g.setColor(Color.BLACK); | |
g.draw(s); | |
} | |
private double mx(MouseEvent e) { return ((double)e.getX()) / (getWidth() / WIDTH ); } | |
private double my(MouseEvent e) { return ((double)e.getY()) / (getHeight() / HEIGHT); } | |
private double dist(double x1, double y1, double x2, double y2) { | |
double dx = x1 - x2; | |
double dy = y1 - y2; | |
return Math.sqrt((dx*dx) + (dy*dy)); | |
} | |
private double dist(Entity a, Entity b) { | |
return dist(a.x, a.y, b.x, b.y); | |
} | |
private <E extends Entity> E closest(List<E> sources, Entity target) { | |
E best = sources.get(0); | |
double bestd = Double.POSITIVE_INFINITY; | |
for(E e : sources) { | |
double dist = dist(target, e); | |
if (dist < bestd) { | |
bestd = dist; | |
best = e; | |
} | |
} | |
return best; | |
} | |
private Shape trans(GeneralPath p, double x, double y, double r, double s) { | |
AffineTransform a = new AffineTransform(); | |
a.translate(x, y); | |
a.rotate(r); | |
a.scale(s, s); | |
return p.createTransformedShape(a); | |
} | |
private void tickAndCull(Collection<? extends Entity> entities, boolean boundCheck) { | |
Set<Entity> killed = new HashSet<Entity>(); | |
for(Entity e : entities) { | |
e.tick(); | |
if (boundCheck && ( | |
(e.x < -10) || (e.x > WIDTH + 10) || | |
(e.y < -10) || (e.y > HEIGHT + 10))) { | |
e.dead = true; | |
} | |
if (e.dead) { | |
killed.add(e); | |
} | |
} | |
entities.removeAll(killed); | |
} | |
private void drawNumber(Graphics g, int x, int y, long num) { | |
do { | |
g.drawImage(Data.nums[(int)(num % 10)], x, y, null); | |
x -= 4; | |
num /= 10; | |
} while(num > 0); | |
} | |
abstract class Entity implements MouseListener, MouseMotionListener { | |
public void mouseEntered (MouseEvent e) {} | |
public void mouseExited (MouseEvent e) {} | |
public void mouseClicked (MouseEvent e) {} | |
public void mousePressed (MouseEvent e) { mouseDown(e.getButton() == MouseEvent.BUTTON1); } | |
public void mouseReleased(MouseEvent e) { mouseUp (e.getButton() == MouseEvent.BUTTON1); } | |
public void mouseDragged (MouseEvent e) { mouseMoved(mx(e), my(e)); } | |
public void mouseMoved (MouseEvent e) { mouseMoved(mx(e), my(e)); } | |
void mouseDown(boolean primary) {} | |
void mouseUp (boolean primary) {} | |
void mouseMoved(double x, double y) {} | |
boolean collide() { return false; } | |
void move(double dx, double dy) { | |
double dist = dist(0, 0, dx, dy); | |
int steps = (int)(dist / 2); | |
if (dist < .001) { return; } | |
double ux = dx / steps; | |
double uy = dy / steps; | |
// do a zig-zag linear interpolation: | |
int ax = steps; | |
int ay = steps; | |
while(!dead && (ax > 0 || ay > 0)) { | |
if (ax > 0) { | |
x += ux; | |
if (collide()) { ax=0; x-= ux; } | |
else { ax--; } | |
} | |
if (ay > 0) { | |
y += uy; | |
if (collide()) { ay=0; y-= uy; } | |
else { ay--; } | |
} | |
} | |
} | |
void moveAngle(double angle, double mag) { | |
move(mag * Math.cos(angle), mag * Math.sin(angle)); | |
} | |
double face(Entity e) { return Math.atan2(e.y - y, e.x - x); } | |
double skew(double width) { return Math.random() * width - (width / 2); } | |
double x; | |
double y; | |
boolean dead = false; | |
abstract void tick(); | |
abstract void draw(Graphics2D g); | |
} | |
/** | |
* Game logic and global state: | |
**/ | |
int mode = 0; | |
Ship player = null; | |
Shape shield = null; | |
boolean hit = false; | |
int hits = 30; | |
long time = 0; | |
int waveno = 0; | |
Random sequence = null; | |
List<Bullet> bullets = new ArrayList<Bullet>(); | |
List<Particle> particles = new ArrayList<Particle>(); | |
List<Enemy> enemies = new ArrayList<Enemy>(); | |
List<Person> people = new ArrayList<Person>(); | |
List<Shape> walls = new ArrayList<Shape>(); { | |
walls.add(new Rectangle(WIDTH / 4 * 1 - 8, HEIGHT / 4 * 1 - 8, 16, 16)); | |
walls.add(new Rectangle(WIDTH / 4 * 3 - 8, HEIGHT / 4 * 1 - 8, 16, 16)); | |
walls.add(new Rectangle(WIDTH / 4 * 1 - 8, HEIGHT / 4 * 3 - 8, 16, 16)); | |
walls.add(new Rectangle(WIDTH / 4 * 3 - 8, HEIGHT / 4 * 3 - 8, 16, 16)); | |
} | |
void newgame() { | |
mode = 0; | |
shield = null; | |
hit = false; | |
hits = 30; | |
time = 0; | |
waveno = 0; | |
bullets .clear(); | |
particles.clear(); | |
enemies .clear(); | |
people .clear(); | |
people.add(new Person( 50, 50)); | |
people.add(new Person(100, 50)); | |
sequence = new Random(0xDEFACED); | |
if (player != null) { | |
removeMouseListener(player); | |
removeMouseListener(player); | |
} | |
player = new Ship(); | |
addMouseListener(player); | |
addMouseMotionListener(player); | |
} | |
void spawn(int type) { | |
switch(type) { | |
case 0 : enemies.add(new Hex()); break; | |
case 1 : enemies.add(new Square(100)); break; | |
case 2 : enemies.add(new Square(200)); break; | |
case 3 : enemies.add(new Square(300)); break; | |
case 4 : enemies.add(new Pent(0)); break; | |
case 5 : enemies.add(new Pent(1)); break; | |
case 6 : enemies.add(new Pent(2)); break; | |
case 7 : enemies.add(new Pent(3)); break; | |
} | |
} | |
void spawnWave() { | |
while(enemies.size() < (waveno / 5) + 1) { | |
switch(sequence.nextInt(19)) { | |
case 0 : spawn(0); break; | |
case 1 : spawn(1); break; | |
case 2 : spawn(1); spawn(2); break; | |
case 3 : spawn(1); spawn(2); spawn(3); break; | |
case 4 : spawn(4); break; | |
case 5 : spawn(5); break; | |
case 6 : spawn(6); break; | |
case 7 : spawn(7); break; | |
case 8 : spawn(4); spawn(4); break; | |
case 9 : spawn(5); spawn(5); break; | |
case 10 : spawn(6); spawn(6); break; | |
case 11 : spawn(7); spawn(7); break; | |
case 12 : spawn(0); spawn(0); break; | |
case 13 : spawn(0); spawn(2); spawn(3); break; | |
case 14 : spawn(4); spawn(4); break; | |
case 15 : spawn(5); spawn(5); break; | |
case 16 : spawn(6); spawn(6); break; | |
case 17 : spawn(7); spawn(7); break; | |
case 18 : spawn(0); break; | |
} | |
} | |
waveno++; | |
} | |
void tick() { | |
if (mode == 0) { | |
player.tick(); | |
for(Person p : people) { p.tick(); } | |
tickAndCull(particles, true); | |
tickAndCull(bullets, true); | |
tickAndCull(enemies, false); | |
if (hit == true) { hits--; } | |
if (hits < 1) { mode = 50; } | |
if (enemies.size() < 1) { spawnWave(); } | |
time++; | |
} | |
else { | |
if (mode > 1) { mode--; } | |
} | |
} | |
void draw(Graphics2D g) { | |
g.setColor(Color.WHITE); | |
g.fillRect(0, 0, getWidth(), getHeight()); | |
g.setColor(Color.BLACK); | |
{ | |
Graphics2D t = (Graphics2D)g.create(); | |
t.setPaint(Data.diagonal); | |
for(Shape s : walls) { t.fill(s); } | |
} | |
for(Person p : people ) { p.draw(g); } | |
for(Bullet b : bullets) { b.draw(g); } | |
g.setXORMode(Color.WHITE); | |
for(Particle p : particles) { p.draw(g); } | |
g.setPaintMode(); | |
for(Enemy e : enemies) { e.draw(g); } | |
player.draw((Graphics2D)g.create()); | |
g.setXORMode(Color.WHITE); | |
g.setColor(Color.BLACK); | |
for(int z = 0; z < hits; z++) { | |
g.fill(new Rectangle( | |
(int)((WIDTH / 2) - (hits / 2 * 3) + (z * 3)), | |
(int)(HEIGHT - 4), | |
1, | |
3 | |
)); | |
} | |
drawNumber(g, WIDTH - 4, 1, time); | |
drawNumber(g, WIDTH - 4, 6, waveno); | |
if (hit) { | |
g.fillRect(0, 0, getWidth(), getHeight()); | |
hit = false; | |
} | |
} | |
/** | |
* Game entities: | |
**/ | |
class Ship extends Entity { | |
Shape bounds = Data.arrow; | |
Muscle shieldup = new Muscle(0.1, 1.0, 1.5); | |
Muscle gather = new Muscle(1.0, 1.2, 1.0); | |
Person picked = null; | |
boolean hit = false; | |
double dir; | |
double ox, oy; | |
Ship() { | |
x = -100; | |
y = -100; | |
} | |
void tick() { | |
shieldup.step(0.1); | |
gather .step(0.1); | |
if (gather.active()) { | |
if (picked == null) { | |
picked = closest(people, this); | |
if (dist(picked, this) < 50) { | |
picked.pick(); | |
} | |
else { | |
picked = null; | |
} | |
} | |
else { | |
double dist = dist(picked, this); | |
if (dist > 10) { | |
picked.move( | |
(x - picked.x) * .125, | |
(y - picked.y) * .125 | |
); | |
picked.dir = picked.face(player); | |
} | |
} | |
} | |
else { | |
if (picked != null) { picked.picked = false; } | |
picked = null; | |
} | |
} | |
void draw(Graphics2D g) { | |
g.setColor(Color.BLACK); | |
if (shieldup.active()) { | |
shield = trans(Data.shield, x, y, dir, shieldup.v()); | |
if (hit) { | |
outline(g, shield); | |
hit = false; | |
} | |
else { | |
g.setXORMode(Color.WHITE); | |
g.fill(shield); | |
g.setPaintMode(); | |
} | |
} | |
else { | |
shield = null; | |
} | |
bounds = trans(Data.arrow, x, y, dir, gather.v()); | |
if (gather.active()) { | |
outline(g, bounds); | |
} | |
else { | |
g.fill(bounds); | |
} | |
} | |
void mouseDown(boolean p) { | |
if (mode == 0) { | |
(p ? shieldup : gather).forward = true; | |
} | |
else { | |
newgame(); | |
} | |
} | |
void mouseUp (boolean p) { | |
if (mode == 0) { | |
(p ? shieldup : gather).forward = false; | |
} | |
} | |
void mouseMoved(double mx, double my) { | |
x = mx; | |
y = my; | |
if (dist(x, y, ox, oy) < 2) { return; } | |
dir = Math.atan2(y - oy, x - ox); | |
ox = (x + ox)/2; | |
oy = (y + oy)/2; | |
} | |
} | |
class Person extends Entity { | |
Shape bounds = Data.arrow; | |
Muscle breathe = new Muscle(0.7, 0.9, 3); | |
Muscle blink = new Muscle(0.9, 2.0, 1); | |
double dir; | |
boolean picked = false; | |
Person(double x, double y) { | |
this.x = x; | |
this.y = y; | |
} | |
void tick() { | |
if (blink.active() && blink.done()) { | |
blink.forward = false; | |
blink.i = 0; | |
} | |
blink.step(0.1); | |
if (breathe.done()) { breathe.forward = !breathe.forward; } | |
breathe.step(0.1); | |
for(Person p : people) { | |
if (p == this) { continue; } | |
if (dist(this, p) < 8) { | |
p.moveAngle(face(p), 2.5); | |
} | |
} | |
if (!picked) { | |
double goaldir = face(player); | |
if (dir < goaldir) { dir += .05; } | |
if (dir > goaldir) { dir -= .05; } | |
dir %= Math.PI * 2; | |
} | |
} | |
boolean collide() { | |
for(Shape s : walls) { | |
if (s.contains(x, y)) { return true; } | |
} | |
return (x <= 5) || (x >= WIDTH-5) || (y <= 5) || (y >= HEIGHT-5); | |
} | |
void draw(Graphics2D g) { | |
if (blink.active()) { | |
outline(g, trans(Data.arrow, x, y, dir, blink.v())); | |
} | |
bounds = trans(Data.arrow, x, y, dir, breathe.v()); | |
outline(g, bounds); | |
} | |
void pick() { | |
picked = true; | |
blink .forward = true; | |
breathe.forward = false; | |
} | |
} | |
class Bullet extends Entity { | |
double a; // angle | |
double v; // velocity (pixels/tick) | |
Bullet(double x, double y, double a, double v) { | |
this.x = x; | |
this.y = y; | |
this.a = a; | |
this.v = v; | |
} | |
boolean collide() { | |
if (shield != null && shield.contains(x, y)) { | |
player.hit = true; | |
player.shieldup.step(-.05); | |
dead = true; | |
return true; | |
} | |
for(Shape s : walls) { | |
if (s.contains(x, y)) { | |
dead = true; | |
return true; | |
} | |
} | |
for(Person p : people) { | |
if (p.bounds.contains(x, y)) { | |
p.moveAngle(a + skew(Math.PI / 4), 1.5 * v); | |
p.blink.forward = true; | |
hit = true; | |
dead = true; | |
return true; | |
} | |
} | |
if (player.bounds.contains(x, y)) { | |
hit = true; | |
dead = true; | |
return true; | |
} | |
return false; | |
} | |
void tick() { | |
moveAngle(a, v); | |
if (dead) { | |
if (Math.random() > .9) { | |
dead = false; | |
v = -v; | |
a += skew(Math.PI / 8); | |
} | |
else { | |
for(int z = (int)(5 * Math.random()); z >= 0; z--) { | |
particles.add(new Particle( | |
x + 2 * Math.random() - 1, | |
y + 2 * Math.random() - 1, | |
a + skew(Math.PI / 4), | |
-(v * 0.50 + (0.50 * Math.random())), | |
5 | |
)); | |
} | |
} | |
} | |
} | |
void draw(Graphics2D g) { | |
g.setColor(Color.BLACK); | |
g.drawLine( | |
(int)x, | |
(int)y, | |
(int)(-v * Math.cos(a) + x), | |
(int)(-v * Math.sin(a) + y) | |
); | |
} | |
} | |
class Particle extends Bullet { | |
int timer; | |
Particle(double x, double y, double a, double v, int t) { | |
super(x, y, a, v); | |
this.timer = t; | |
} | |
boolean collide() { | |
return false; | |
} | |
void tick() { | |
moveAngle(a, v); | |
timer--; | |
dead = timer < 1; | |
} | |
} | |
abstract class Enemy extends Entity { | |
Muscle fire = new Muscle(0.8, 1.2, 1); | |
double dir; | |
double gx; | |
double gy; | |
Enemy() { | |
dir = Math.random() * 2 * Math.PI; | |
pickOrigin(); | |
this.x = gx; | |
this.y = gy; | |
pickGoal(); | |
} | |
void pickOrigin() { | |
double ang = Math.random() * 2 * Math.PI; | |
gx = (WIDTH / 2) + (WIDTH * Math.cos(ang)); | |
gy = (HEIGHT / 2) + (HEIGHT * Math.sin(ang)); | |
} | |
void pickGoal() { | |
gx = Math.random() * (WIDTH * .8) + (WIDTH * .1); | |
gy = Math.random() * (HEIGHT * .8) + (HEIGHT * .1); | |
} | |
void tick() { | |
fire.step(.1); | |
if (fire.done() && fire.forward) { fire.forward = false; } | |
if (dist(x, y, gx, gy) < 10) { pickGoal(); } | |
x += (gx - x) * .0125; | |
y += (gy - y) * .0125; | |
} | |
void draw(Graphics2D g, GeneralPath s, boolean blink) { | |
Shape t = trans(s, x, y, dir, fire.v()); | |
if (blink) { outline(g, t); } | |
else { g.fill(trans(s, x, y, dir, fire.v())); } | |
} | |
} | |
class Square extends Enemy { | |
double vx; | |
double vy; | |
Square(double dist) { | |
dir = Math.random() * 2 * Math.PI; | |
this.x = (WIDTH / 2) - dist * Math.cos(dir); | |
this.y = (HEIGHT / 2) - dist * Math.sin(dir); | |
this.gx = (WIDTH / 2) + dist * Math.cos(dir); | |
this.gy = (HEIGHT / 2) + dist * Math.sin(dir); | |
this.vx = 1.2 * Math.cos(dir); | |
this.vy = 1.2 * Math.sin(dir); | |
} | |
void tick() { | |
fire.step(.1); | |
if (fire.done() && fire.forward) { fire.forward = false; } | |
if (Math.random() > .95) { | |
bullets.add(new Bullet(x, y, face(player), 2.2)); | |
fire.forward = true; | |
} | |
dir += .3; | |
x += vx; | |
y += vy; | |
if (dist(x, y, gx, gy) < 10) { dead = true; } | |
} | |
void draw(Graphics2D g) { | |
draw(g, Data.square, true); | |
} | |
} | |
class Pent extends Enemy { | |
int timer = 80; | |
int pattern; | |
// pattern 0 - fireburst | |
// pattern 1 - cone | |
// pattern 2 - spiral | |
// pattern 3 - double spiral | |
Pent(int pattern) { | |
fire.a = 1.0; | |
fire.b = 0.5; | |
this.pattern = pattern; | |
} | |
void explode() { | |
for(int z = 0; z < 100; z++) { | |
bullets.add(new Bullet(x, y, Math.PI * 2 / 100 * z, 3.0)); | |
} | |
} | |
void cone() { | |
double target = face(closest(people, this)); | |
for(int z = -10; z < 10; z++) { | |
bullets.add(new Bullet(x, y, target + (z * Math.PI / 40), 2.3)); | |
bullets.add(new Bullet(x, y, target + (z * Math.PI / 40), 2.5)); | |
bullets.add(new Bullet(x, y, target + (z * Math.PI / 40), 3.0)); | |
} | |
} | |
void fire() { | |
bullets.add(new Bullet(x, y, dir, 2.5)); | |
if (pattern == 3) { | |
bullets.add(new Bullet(x, y, dir + Math.PI, 2.5)); | |
} | |
} | |
void tick() { | |
if (dist(x, y, gx, gy) < 10) { | |
dir += .05; | |
if (pattern == 2 || pattern == 3) { fire(); } | |
if (timer < 20) { | |
dir += .05; | |
} | |
if (timer < 10) { | |
fire.forward = true; | |
fire.step(.1); | |
} | |
if (timer < 1) { | |
if (pattern == 0) { explode(); } | |
if (pattern == 1) { cone(); } | |
dead = true; | |
} | |
else { timer--; } | |
} | |
else { | |
super.tick(); | |
} | |
} | |
void draw(Graphics2D g) { | |
boolean blink = ((timer < 40) ? (timer / 2) : (timer / 4)) % 2 == 0; | |
draw(g, Data.pent, blink); | |
} | |
} | |
class Hex extends Enemy { | |
int hops = 6; | |
void pickGoal() { | |
super.pickGoal(); | |
hops--; | |
if (hops == 1) { | |
pickOrigin(); | |
} | |
if (hops == 0) { | |
dead = true; | |
} | |
} | |
void tick() { | |
super.tick(); | |
Person p = closest(people, this); | |
if (Math.random() > .8) { | |
bullets.add(new Bullet( | |
x, y, | |
face(p) + skew(Math.PI / 8), | |
4.0 | |
)); | |
fire.forward = true; | |
} | |
dir += .05; | |
} | |
void draw(Graphics2D g) { | |
draw(g, Data.hex, true); | |
} | |
} | |
} | |
/** | |
* Game data: | |
**/ | |
class Data { | |
static GeneralPath ngon(int sides, double radius) { | |
Polygon p = new Polygon(); | |
for(int z = 0; z < sides; z++) { | |
p.addPoint( | |
(int)(radius * Math.cos(z * Math.PI * 2 / sides)), | |
(int)(radius * Math.sin(z * Math.PI * 2 / sides)) | |
); | |
} | |
return new GeneralPath(p); | |
} | |
static BufferedImage render(int w, int h, int... data) { | |
BufferedImage ret = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); | |
Graphics g = ret.getGraphics(); | |
for(int z = 0; z < w*h; z++) { | |
g.setColor(data[z] == 0 ? Color.WHITE : Color.BLACK); | |
g.drawLine(z%w,z/w,z%w,z/w); | |
} | |
return ret; | |
} | |
static final GeneralPath arrow = new GeneralPath(new Polygon( | |
new int[] { 4,-4,-4}, | |
new int[] { 0, 3,-3}, | |
3 | |
)); | |
static final GeneralPath hex = ngon(6, 7.0); | |
static final GeneralPath pent = ngon(5, 6.5); | |
static final GeneralPath square = ngon(4, 5.0); | |
static final GeneralPath shield; static { | |
Polygon p = new Polygon(); | |
for(int z = -5; z <= 5; z++) { | |
p.addPoint( | |
(int)(16 * Math.cos(z * Math.PI/16)), | |
(int)(16 * Math.sin(z * Math.PI/16)) | |
); | |
} | |
for(int z = 5; z >= -5; z--) { | |
p.addPoint( | |
(int)(12 * Math.cos(z * Math.PI/16)), | |
(int)(12 * Math.sin(z * Math.PI/16)) | |
); | |
} | |
shield = new GeneralPath(p); | |
} | |
static final Image[] nums = { | |
render(3,4, 1,1,1, 1,0,1, 1,0,1, 1,1,1 ), | |
render(3,4, 0,1,0, 0,1,0, 0,1,0, 0,1,0 ), | |
render(3,4, 1,1,1, 0,0,1, 0,1,0, 1,1,1 ), | |
render(3,4, 1,1,1, 0,1,1, 0,0,1, 1,1,1 ), | |
render(3,4, 1,0,1, 1,0,1, 1,1,1, 0,0,1 ), | |
render(3,4, 1,1,1, 1,1,0, 0,0,1, 1,1,0 ), | |
render(3,4, 1,0,0, 1,1,1, 1,0,1, 1,1,1 ), | |
render(3,4, 1,1,1, 0,0,1, 0,1,0, 0,1,0 ), | |
render(3,4, 1,1,1, 1,0,1, 1,1,1, 1,1,1 ), | |
render(3,4, 1,1,1, 1,0,1, 1,1,1, 0,0,1 ), | |
}; | |
static final Paint diagonal = new TexturePaint( | |
render(4,4, 1,0,0,1, 1,1,0,0, 0,1,1,0, 0,0,1,1 ), | |
new Rectangle(4,4) | |
); | |
} | |
class Muscle { | |
double a; // start position | |
double b; // end position | |
double t; // tween duration | |
double i; // tween index | |
boolean forward = false; // moving forward? | |
Muscle(double start, double end, double time) { | |
a = start; | |
b = end; | |
t = time; | |
} | |
void step(double time) { | |
i = forward ? (i + time) : (i - time); | |
i = Math.max(i, 0); | |
i = Math.min(i, t); | |
} | |
boolean active() { | |
return !done() || forward; | |
} | |
boolean done() { | |
return forward ? (i == t) : (i == 0); | |
} | |
double v() { | |
return a * tween(1 - (i/t)) + | |
b * tween( (i/t)); | |
} | |
double tween(double index) { | |
// a hand-tuned sigmoid eased tween: | |
return 1/(1 + Math.pow(Math.E, -12*(index-.5))); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment