-
Notifications
You must be signed in to change notification settings - Fork 1
/
Greep.java
598 lines (532 loc) · 17.2 KB
/
Greep.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
import greenfoot.*; // (World, Actor, GreenfootImage, and Greenfoot)
import java.util.List;
import java.util.Iterator;
import java.util.ArrayList;
/**
* A Greep is the base class for all alien beings in this scenario. It
* provides the basic abilities of greeps in this world.
*
* @author Michael Kolling
* @author Davin McCall
* @author Poul Henriksen
* @version 2.0
*/
public abstract class Greep extends Actor
{
// Constants
private static final double WALKING_SPEED = 5.0;
private static final int TIME_TO_SPIT = 10;
private static final int KNOCK_OUT_TIME = 70;
private static final int VISION_RANGE = 70;
private static final int MODE_WALKING = 0;
private static final int MODE_BLOCKING = 1;
private static final int MODE_FLIPPED = 2;
/** Indicate whether we have a tomato with us */
private boolean carryingTomato = false;
/** The greep's home ship */
private Ship ship;
/** General state */
private boolean moved = false;
private boolean atWater = false;
private boolean moveWasBlocked = false;
private int mode = MODE_WALKING;
private int timeToKablam = 0;
/** The time at which the block sound was last played. */
private long blockSoundTime = 0;
/** if flipped, will slide for some way */
int slideSpeed = 0;
int spinSpeed = 0;
int slideDirection = 0;
/** General purpose memory */
private int[] memory = new int[4];
private boolean[] flags = new boolean[2];
/**
* Create a greep.
*/
public Greep(Ship ship)
{
this.ship = ship;
setRotation(Greenfoot.getRandomNumber(360));
setImage(getCurrentImage());
}
/**
* Greenfoot's standard act method, which can be reimplemented in subclasses.
* When reimplemented, the first line of the act() should always call super.act().
*/
public void act()
{
moved = false;
if (mode == MODE_FLIPPED) {
if (slideSpeed != 0 || spinSpeed != 0) {
if (slideSpeed != 0) {
if(slideSpeed < 20) {
// Drop the tomato after we have been sliding for a while
leaveTomato();
}
double angle = Math.toRadians(slideDirection);
int speed = slideSpeed / 10;
int x = (int) Math.round(getX() + Math.cos(angle) * speed);
int y = (int) Math.round(getY() + Math.sin(angle) * speed);
if (canMoveTo(x,y)) {
setLocation(x,y);
}
else {
slideDirection = slideDirection + 80 + Greenfoot.getRandomNumber(20);
}
slideSpeed = Math.max((int)(slideSpeed * .95 - 1), 0);
}
if (spinSpeed != 0) {
setRotation(getRotation() + spinSpeed);
spinSpeed--;
}
}
else
if (Greenfoot.getRandomNumber(KNOCK_OUT_TIME) == 0) {
mode = MODE_WALKING;
setImage(getCurrentImage());
}
}
else if(mode == MODE_BLOCKING) {
turn(3);
}
else {
if (timeToKablam > 0) {
timeToKablam--;
}
}
}
/**
* This method specifies the name of the greeps (for display on the result board).
* Try to keep the name short so that it displays nicely on the result board.
*/
abstract public String getName();
/**
* Turn 'angle' degrees towards the right (clockwise).
*/
public final void turn(int angle)
{
if(mode == MODE_FLIPPED) return;
setRotation(getRotation() + angle);
}
/**
* Turn in the direction facing the home ship.
*/
protected final void turnHome()
{
if(mode == MODE_FLIPPED) return;
turnTowards(ship.getX(), ship.getY());
}
/**
* Turn to face an arbitrary point on the map.
*/
public final void turnTowards(int x, int y)
{
if(mode == MODE_FLIPPED) return;
int deltaX = x - getX();
int deltaY = y - getY();
setRotation(getAngleTo(deltaX, deltaY));
}
/**
* True if we are at our space ship.
*/
protected final boolean atShip()
{
return ship == getOneIntersectingObject(Ship.class);
}
/**
* Get the ship's data bank array (1000 ints).
* This can only be done if the greep is at the ship.
* The data inside the array can be freely manipulated.
*/
protected final int[] getShipData()
{
if (atShip()) {
return ship.getData();
}
else {
return null;
}
}
/**
* Try and move forward in the current direction. If we are blocked (by water, or an
* opponent greep who is blocking), we won't move; atWater()/moveWasBlocked() can be
* used to check for this, and in that case, it is allowed to change direction and try
* move()ing again.
*/
protected final void move()
{
if(moved) // can move only once per 'act' round
return;
if (mode == MODE_FLIPPED)
return;
if(mode != MODE_WALKING) {
mode = MODE_WALKING; // can't be blocking if we're moving
setImage(getCurrentImage());
}
double angle = Math.toRadians( getRotation() );
int x = (int) Math.round(getX() + Math.cos(angle) * WALKING_SPEED);
int y = (int) Math.round(getY() + Math.sin(angle) * WALKING_SPEED);
if (canMoveTo(x,y)) {
setLocation(x, y);
moved = true;
}
}
/**
* Return true if we have just seen water in front of us.
* The edge of the map is also water.
*/
protected final boolean atWater()
{
return atWater;
}
/**
* Return true if we have been blocked by an opponent greep.
*/
protected final boolean moveWasBlocked()
{
return moveWasBlocked;
}
/**
* Load a tomato onto *another* greep. This works only if there is another greep
* and a tomato pile present, otherwise this method does nothing.
*/
protected final void loadTomato()
{
if(mode == MODE_FLIPPED) return;
// check whether there's a tomato pile here
TomatoPile tomatoes = getTomatoes();
// check whether there's another friendly greep here
List friendlies = getObjectsInRange(10, this.getClass());
if(! friendlies.isEmpty() && tomatoes != null) {
Greep greep = (Greep) friendlies.iterator().next();
if(greep.ship == this.ship && !greep.carryingTomato()) {
tomatoes.takeOne();
greep.carryTomato();
}
}
}
/**
* Check whether we are carrying a tomato.
*/
protected final boolean carryingTomato()
{
return carryingTomato;
}
/**
* Drop the tomato we are carrying. If we are at the ship, it is counted.
* If not, it's just gone...
*/
protected final void dropTomato()
{
if(!carryingTomato)
return;
if(atShip()) {
ship.storeTomato();
}
carryingTomato = false;
setImage(getCurrentImage());
}
/**
* If we can see tomatoes, this will return them. Otherwise it returnes null.
* <p>
* You are only allowed to call getX() and getY() on the returned tomato pile.
*/
protected final TomatoPile getTomatoes()
{
TomatoPile tomatoes = (TomatoPile) getOneIntersectingObject(TomatoPile.class);
if (tomatoes != null) {
if (distanceTo(tomatoes.getX(), tomatoes.getY()) < 25 && !tomatoes.isEmpty()) {
return tomatoes;
}
}
return null;
}
/**
* Return the number of nearby opponent greeps which are not currently knocked out by a stink bomb.
*
* @param withTomatoes If true, only count the greeps that are carrying a tomato.
*/
protected final int numberOfOpponents(boolean withTomatoes)
{
int count = 0;
List l = getObjectsInRange(VISION_RANGE, Greep.class);
for (Iterator i = l.iterator(); i.hasNext(); ) {
Greep greep = (Greep) i.next();
if (greep.ship != ship) {
// It's an enemy greep
if (greep.mode != MODE_FLIPPED && (!withTomatoes || greep.carryingTomato()))
count++;
}
}
return count;
}
/**
* Return the number of nearby friendly greeps which are not currently knocked out by a stink bomb.
*
* @param withTomatoes If true, only count the greeps that are carrying a tomato.
*/
protected final int numberOfFriends(boolean withTomatoes)
{
int count = 0;
List l = getObjectsInRange(VISION_RANGE, this.getClass());
for (Iterator i = l.iterator(); i.hasNext(); ) {
Greep greep = (Greep) i.next();
if (greep.ship == ship) {
// It's a friendly greep
if( greep.mode != MODE_FLIPPED && (!withTomatoes || greep.carryingTomato()))
count++;
}
}
return count;
}
/**
* Returns a friendly greep, if there is one at our current location.
* Returns null otherwise.
* <p>
* You are only allowed to access the memory and flags of the friend.
*/
protected final Greep getFriend()
{
List greeps = getObjectsInRange(30, this.getClass());
for(Object obj : greeps) {
Greep greep = (Greep) obj;
if(greep.ship == this.ship) {
return greep;
}
}
return null;
}
/**
* Return 'true' in exactly 'percent' number of calls. That is: a call
* randomChance(25) has a 25% chance to return true.
*/
protected final boolean randomChance(int percent)
{
return Greenfoot.getRandomNumber(100) < percent;
}
/**
* Store something in the greep's memory. There are four memory slots, numbered
* 0 to 3, each can hold an int value.
*/
protected final void setMemory(int slot, int val)
{
memory[slot] = val;
}
/**
* Retrieve a previously stored value.
*
* Other friendly greeps are allowed to call this mehtod.
*/
public final int getMemory(int slot)
{
return memory[slot];
}
/**
* Store a user defined boolean value (a "flag"). Two flags are available,
* i.e. 'flagNo' may be 1 or 2.
*/
protected final void setFlag(int flagNo, boolean val)
{
if(flagNo < 1 || flagNo > 2)
throw new IllegalArgumentException("flag number must be either 1 or 2");
else
flags[flagNo-1] = val;
}
/**
* Retrieve the value of a flag. 'flagNo' can be 1 or 2.
*
* Other friendly greeps are allowed to call this mehtod.
*/
public final boolean getFlag(int flagNo)
{
if(flagNo < 1 || flagNo > 2)
throw new IllegalArgumentException("flag number must be either 1 or 2");
else
return flags[flagNo-1];
}
/**
* Return true if this greep is in "blocking" mode - it has hunkered down to
* prevent opponent greeps from passing (while allowing friendly greeps through).
*/
protected final boolean isBlocking()
{
return mode == MODE_BLOCKING;
}
/**
* Block opponent greeps from passing our current location. This is only effective if
* we haven't moved (can't move and block in the same turn).
*/
protected final void block()
{
if (moved)
return;
if (mode == MODE_FLIPPED)
return;
if(!isBlocking()) {
long dt = System.currentTimeMillis() - blockSoundTime;
if(dt > 1000 && Earth.PLAY_SOUNDS) {
Greenfoot.playSound("greepBlock.wav");
blockSoundTime = System.currentTimeMillis();
}
mode = MODE_BLOCKING;
setImage(getCurrentImage());
}
}
/**
* Release a stink bomb. All greeps within a small radius will be knocked out for
* a small period of time. If a greep carrying a tomato is knocked out, it will drop
* the tomato on the ground.
*/
protected final void kablam()
{
if (mode == MODE_FLIPPED) {
return;
}
if (timeToKablam > 0) {
return;
}
if(Earth.PLAY_SOUNDS) {
Greenfoot.playSound("greepFart.wav");
}
timeToKablam = 20; // prevent total carnage
List l = getObjectsInRange(100, Greep.class);
for (Iterator i = l.iterator(); i.hasNext(); ) {
Greep greep = (Greep) i.next();
greep.keelOver();
greep.slideSpeed = 20 + Greenfoot.getRandomNumber(120);
greep.spinSpeed = Greenfoot.getRandomNumber(70) + 10;
greep.slideDirection = Greenfoot.getRandomNumber(360);
}
keelOver();
slideSpeed = 20 + Greenfoot.getRandomNumber(120);
spinSpeed = Greenfoot.getRandomNumber(70) + 10;
slideDirection = Greenfoot.getRandomNumber(360);
getWorld().addObject(new Smoke(5, isTeamTwo()), getX(), getY());
}
/**
* True if we can move to the given location without being blocked by water, the edge
* of the map or a blocking opponent.
*/
private boolean canMoveTo(int x, int y)
{
atWater = false;
moveWasBlocked = false;
if(x >= getWorld().getWidth()) {
atWater = true;
}
if(x < 0) {
atWater = true;
}
if(y >= getWorld().getHeight()) {
atWater = true;
}
if(y < 0) {
atWater = true;
}
if(! atWater && ((Earth)getWorld()).isWater(x, y)) {
atWater = true;
}
moveWasBlocked = false;
if (! atWater) {
List otherGreeps = getWorld().getObjectsAt(x,y,Greep.class);
for (Iterator i = otherGreeps.iterator(); i.hasNext(); ) {
Greep otherGreep = (Greep) i.next();
if (otherGreep.ship != this.ship) {
if (otherGreep.blocks(x,y) && ! otherGreep.blocks(getX(), getY())) {
moveWasBlocked = true;
break;
}
}
}
}
return !(atWater || moveWasBlocked);
}
/**
* True if this greep is blocking the given position.
*/
private boolean blocks(int x, int y)
{
return isBlocking() && distanceTo(x,y) < 30;
}
/**
* Receive a tomato and carry it.
*/
private void carryTomato()
{
carryingTomato = true;
setImage(getCurrentImage());
}
/**
* Leave the tomato we are carrying.
* It will put the tomato on the ground - forming a pile of one tomato.
*/
private void leaveTomato()
{
if(!carryingTomato)
return;
getWorld().addObject(new TomatoPile(1), getX(), getY());
carryingTomato = false;
setImage(getCurrentImage());
}
/**
* Make this greep flip.
*/
private void keelOver()
{
mode = MODE_FLIPPED;
setImage(getCurrentImage());
}
/**
* This method specifies the image we want displayed at any time.
*/
private String getCurrentImage()
{
String base;
if(isTeamTwo()) {
base = "greep-purple";
}
else {
base = "greep-green";
}
if (mode == MODE_FLIPPED) {
return base + "-flipped.png";
}
else if(mode == MODE_BLOCKING) {
return base + "-blocking.png";
}
else if(carryingTomato()) {
return base + "-with-food.png";
}
else {
return base + ".png";
}
}
/**
* Return true if this is team 2, false if it is team 1.
*/
private boolean isTeamTwo()
{
if(ship == null) {
return false;
}
else {
return ship.isTeamTwo();
}
}
/**
* Return the angle from the origin (0,0) to some point (x,y), in degrees
*/
private int getAngleTo(int x, int y)
{
return (int)(180 * Math.atan2(y, x) / Math.PI);
}
/**
* Return the distance between this greep and an arbitrary point.
*/
private int distanceTo(int x, int y)
{
int deltaX = getX() - x;
int deltaY = getY() - y;
return (int) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
}
}