-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathProgram.cs
1929 lines (1727 loc) · 89.7 KB
/
Program.cs
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
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using System;
using System.Collections.Generic;
using System.Linq;
// Forked off from https://dotnetfiddle.net/Wt0Up8 at its inception (1.0)
namespace ai {
/* Phase 3 : AI
* FRAMEWORK:
* X) Player class needs a bool cpu = false
* X) "Enter name" in Player constructor needs to parse for "(CPU)" as the first 5 chars and if true, mark cpu = true
* 3) Create new AI class, these objects will belong to the players
* b) Needs "consultAI" method to make general decisions, should be passed self & opponent
* c) Needs "consultCharAI" method to check player's character and probe its specific AI preferences (fwds to charAI below)
* i) EACH char ability needs an AI version
* d) Base AI ALSO needs an "AIresponse" method that can answer specific requests from human opponent (Spray Fire & Seizmo)
* e) Needs basicFireAI that will never recur because it doesn't fire on already fired nor illegal targets,
* and resolves and reports to itself its own results
* 4) Each character needs a "charAI" method that will make character-based decisions when called from player's AI
* No char?
* Val?
* Earl?
* Burt?
* Grady?
* 5) If cpu == true at character setup, instantiate AI for the player and skip adding menuOptions
* 6) EVERY prompt for input needs to check cpu == true and if so consultAI for the decision IN LIEU of prompting
* 7) A standalone function to place graboids randomly for setup
*
* BASE LOGIC FOR AI CLASS:
* 1) INITIALIZE
* a) blanket(width) will create a list of coords that checkerboards a board, and do nothing else, just stash the list
* i) Checkerboard is randomly determined to be starting with 0,0 or 0,1 and checker from there
* ii) Whenever you're seeking, only shoot into the blanket, remove blanket items as they are fired on
* iii) Use "width" to determine how far apart diagonally the blanket should be drawn... After DirtD killed, the
* blanket needs to be widened and redrawn because the next smallest graboid is 3 spaces, etc.
* b) int currentHits = 0, every time a hit is returned, currentHits++ (EVENT)
* c) bool killed = false, true when killed (EVENT)
* d) string mode = seek
* e) int strategy = *random
* f) int[] seekDirection, can be 1, 0, or -1, determining whether that axis is unchanging, or which dir advancing
* g) int[] killDirection, can be 1, 0, or -1, determining whether that axis is unchanging, or which dir advancing
* h) list<int[]> seekSet
* i) list<int[]> fireQueue
* j) list<int[]> hitSet
* k) pop(int[], list) will delete a matching coord in given list (always pop blanket and seekSet/hitSet/fireQueue)
* 2) bool consultAI(Player self, Player opponent) method - returns "done" to turn loop
* a) if mode == seek, seek(), else if mode == destroy, destroy();
* 3) SEEK mode
* a) consultCharAI for preferences first
* b) Execute STRATEGY sub mode (asterisks are randomized elem)
* c) int[] newSeek() will randomly choose starting pt from the blanket to start with (given no charAI overrides)
* then scan blanket's values for coords that fit a strategy below & populates seekSet w/ dupes of qualified items
* i) Single *row/column, blanket across *n/e/s/w as seekDirection, *newSeek when seekSet is depleted
* ii) Double *row/column, blanket across *n/e/s/w as seekDirection, *newSeek when seekSet is depleted
* iii) Diagonal cuts, blanket *ne/se/sw/nw, *newSeek when seekSet is depleted
* iv) Prioritize corners *ne/se/sw/nw (ranges 7-9, 0-2), then switch strats to i-iii when range exhausted
* v) Prioritize edges *n/e/s/w (ranges ((0-2 or 7-9)x 3-6)), then switch strats to i-iii when range exhausted
* vi) Prioritize center (ranges 3-6), then switch strats to i-iii when range exhausted
* d) Randomly choose from seekSet and Fire
* i) If missed, iterate the seekSet next turn
* ii) If hit, pass XY into hitSet, currentHits++, switch to destroy mode
* 4) DESTROY mode
* ** COPIOUSLY check for !gameOver after hits
* a) consultCharAI for preferences first
* b) If hitSet.count == 1 && fireQueue.count == 0
* i) Add all spaces adjacent to hitSet[0] *n/e/s/w to fireQueue, Fire randomly *n/e/s/w
* ii) If hitSet.count == 1 > iterate fireQueue until another is a hit push that to hitSet
* c) If hitSet.count == 2 && killDirection == null
* i) Compare hitSet[0] and hitSet[1], series of Ifs to determine horiz or vert (see setCoords validation)
* ii) Clear fireQueue to flush adjacents from first shot
* iii) Set killDirection if not null, increment one more coord in that direction from *hitSet & add to fireQueue
* d) Fire on next in fireQueue
* - hit
* -> kill false? continue (increment one more coord in killDirection, fire)
* -> kill true? (proceed to step "e" below)
* - miss -> flip +1/-1 killDirection for next turn (multiply both coords by -1, cuz it'll be like [0,1])
* e) If killed = Y: currentHits - maxHp graboid killed; if currentHits == 0 (Confirming you didn't hit 2 diff graboids)
* i) true -> RESET: killed = false; clear fireQueue; clear hitSet; mode = seek
* ii) false -> (some algorithm to search the full perimeter of your hitSet)
*
* PLACING GRABOIDS:
*
*/
}
namespace Seizmojigger {
// using ai;
class Program {
// Global functions
static bool debug;
static char gameModeShowBoard;
static int gameModeNoPlayers;
// Converts easy coordinates X# to array coordinates #,#; e.g. B1 to 1,0; Takes X#, returns ints [x,y]
public static int[] ANtoXY(string an) {
// BEAR IN MIND: In real Battleship, coordinates X# are *actually* Y,X coordinates, going to a row then column!!
int[] xy = new int[2];
// -- Bad X or Y coordinates in this function should always return [-1,-1]
// Sanity check - string length
if(an.Length != 2) {
xy[0] = -1;
xy[1] = -1;
}
// Don't bother converting if it already failed the first length check
if(!xy.SequenceEqual(new int[] { -1, -1 })) {
// Set X - convert character to uppercase, then to digit
an = an.ToUpper();
xy[0] = (int) (an[0] - 65);
// Set Y - slightly more complex, expect a number and if you don't get one, set it to -1 to fail later
// Bear in mind that Ys go 0-9, not 1-10, because it's easier to display in ascii
int y;
int.TryParse(an.Substring(1, 1), out y);
if(int.TryParse(an.Substring(1, 1), out y)) {
xy[1] = y;
} else {
xy[1] = -1;
}
// Be sure the final converted coordinates are still in range
if(xy[0] < 0 || xy[0] > 9 || xy[1] < 0 || xy[1] > 9) {
xy[0] = -1;
xy[1] = -1;
}
}
// If there were any errors, report it
if(xy.SequenceEqual(new int[] { -1, -1 })) {
Console.Write("Illegal grid coordinates! Must be in X# format, where X is [A-J], and # is [0-9].");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write(" Retry:\n");
Console.ResetColor();
}
// Always return, because the calling function should loop when [-1,-1] is returned
return xy;
}
// Fetches coordinates for any reason from user
public static int[] getCoords() {
// Initialize coordinates as a fail value
int[] convertedCoords = new int[2] { -1, -1 };
// Keep asking for new coordinates until a valid coordinate in the grid is passed
do {
string givenCoords = Console.ReadLine();
convertedCoords = ANtoXY(givenCoords);
} while(convertedCoords[0] == -1 && convertedCoords[1] == -1);
return convertedCoords;
}
// Fetches the set of all adjacent coordinates; Takes [x,y], returns all eligible adjacent coords
public static int[][] getAdjCoords(int[] c) {
// If [-1,-1] was passed, quit now
if(c.SequenceEqual(new int[] { -1, -1 })) {
return null;
}
// Take given coords, and adjust 1 in each direction
List<int[]> adjList = new List<int[]>();
adjList.Add(new int[] { c[0] - 1, c[1] }); // North
adjList.Add(new int[] { c[0], c[1] + 1 }); // East
adjList.Add(new int[] { c[0] + 1, c[1] }); // South
adjList.Add(new int[] { c[0], c[1] - 1 }); // West
// Count the valid ones only
int validC = 0;
foreach(int[] cn in adjList) {
if(cn[0] > -1 && cn[0] < 10 && cn[1] > -1 && cn[1] < 10) {
validC++;
}
}
// Add the valid ones only to a fresh array of coords
int[][] adj = new int[validC][];
int i = 0;
foreach(int[] cn in adjList) {
if(cn[0] > -1 && cn[0] < 10 && cn[1] > -1 && cn[1] < 10) {
adj[i] = cn;
i++;
}
}
return adj;
}
// Defines a menu item
public struct menuItem {
public string label; // Label within an options menu
public Action method; // Method to run
public bool endsTurn; // Whether or not menu should reload turn after running method
public menuItem(string l, Action m, bool e = true) {
label = l;
method = m;
endsTurn = e;
}
}
// Checks a space on the board; Takes [i,i] coordinates, returns [hit/miss,graboidType]
public static string[] target(Board toBoard, int[] c) {
string[] results = new string[2];
results[0] = toBoard.grid[c[0], c[1]].state;
results[1] = toBoard.grid[c[0], c[1]].graboidType;
return results;
}
// Class definitions
// -- Board classes
public class Space {
public string graboidType { get; set; } // 5-char code for what graboid is here, if any, default "null"
public string state { get; set; } // Use "null","miss","hit" / possibly switch this to enums?
// Constructor
public Space() {
this.graboidType = null;
this.state = null;
}
}
public class Board {
public Space[,] grid = new Space[10, 10]; // A 10 x 10 array of spaces
// Constructor
public Board() {
// Initialize array with spaces
for(int x = 0; x < 10; x++) {
for(int y = 0; y < 10; y++) {
grid[x, y] = new Space();
}
}
}
// Displays the board in ascii
public void showAll(char with = 'n') {
for(int lat = 0; lat < 10; lat++) {
Console.Write("| ");
for(int lon = 0; lon < 10; lon++) {
// Label spaces X#
char L = (char) (lat + 65);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write("{0}{1}", L, lon);
Console.ResetColor();
// Display states
if(this.grid[lat, lon].state == null) {
Console.Write(" ");
} else if(this.grid[lat, lon].state == "hit") {
Console.ForegroundColor = ConsoleColor.Red;
Console.Write(" X");
Console.ResetColor();
} else if(this.grid[lat, lon].state == "miss") {
Console.ForegroundColor = ConsoleColor.White;
Console.Write(" o");
Console.ResetColor();
}
// If "with" is 'g' for 'graboids', show graboids and allow space for graboid names
if(with == 'g' && this.grid[lat, lon].graboidType != null) {
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.Write(" " + this.grid[lat, lon].graboidType); // write out the 5 char code
Console.ResetColor();
} else if(with == 'g' && this.grid[lat, lon].graboidType == null) {
Console.Write(" "); // space + 5 spaces for 5 char code blank
}
// Separate columns
Console.Write("| ");
}
Console.WriteLine();
}
}
}
// -- Graboid classes
public class Graboid {
private string _type;
private int _hp;
public readonly int maxHp;
private string _displayName;
public string type { get { return _type; } }
public int hp { get { return _hp; } }
public string displayName { get { return _displayName; } }
public int[][] coordinates { get; set; } // Read as [hp# instances of, [x,y] grid coordinate pair arrays]
// Constructor
public Graboid(string code) {
_type = code;
switch(type) {
case "dirtd":
this._displayName = "Dirt Dragon";
this._hp = 2;
this.maxHp = 2;
break;
case "shrkr":
this._displayName = "Shrieker";
this._hp = 3;
this.maxHp = 3;
break;
case "assbl":
this._displayName = "Ass Blaster";
this._hp = 3;
this.maxHp = 3;
break;
case "grabd":
this._displayName = "Graboid";
this._hp = 4;
this.maxHp = 4;
break;
case "blanc":
this._displayName = "El Blanco";
this._hp = 5;
this.maxHp = 5;
break;
default:
Console.WriteLine("ERROR: Invalid graboid type");
break;
}
coordinates = new int[hp][];
}
// Takes a hit, then returns "true" if it died
public bool takeHit() {
_hp--;
// Show HP notice if debug is on
if(debug) {
Console.WriteLine("{0} took a hit, {1} hp left.", this._displayName, this._hp);
}
if(_hp <= 0) {
return true;
}
return false;
}
}
// -- Ability classes -- UNUSED, but leaving in case I come back to the idea
// A base class/delegate for a passive ability
// A base class/delegate for an activated ability
// character (which holds menuOptions) needs property: #uses, if uses == 0 ? remove option
// -- Generic Character classes
// Basic firing routine; Takes a Player, asks for coords, resolves the whole 9 yards
public static void basicFire(Player p, int[] givenC = null) {
int[] c;
char a;
// If coordinates are given, use those, otherwise prompt for some
if(givenC != null) {
c = givenC;
a = (char) (c[0] + 65);
Console.Write("Firing on {0}{1}: ", a, c[1]);
} else {
Console.Write("Fire: ");
c = getCoords();
a = (char) (c[0] + 65);
}
// On state:null, check for graboid and hit or miss it; On state:hit/miss, cancel
string[] targetStatus = target(p.board, c);
switch(targetStatus[0]) {
case null:
// On graboid present, hit; else, miss
if(targetStatus[1] != null) {
p.board.grid[c[0], c[1]].state = "hit";
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("\nHit!!");
Console.ResetColor();
// Prod player to broadcast a hit event
p.character.getHit();
p.dmgGraboid(targetStatus[1]);
} else {
p.board.grid[c[0], c[1]].state = "miss";
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("\nMiss...");
Console.ResetColor();
}
break;
default:
// Convert XY to AN for screen printing purposes
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("You've already fired on {0}{1}, it was a {2}. Select new coordinates:",
a, c[1], targetStatus[0]);
Console.ResetColor();
basicFire(p);
break;
}
}
// Delegates for alerts
// DELEGATES ARE PROXIES OF OTHER FUNCTIONS
// http://stackoverflow.com/questions/2814065/help-understanding-net-delegates-events-and-eventhandlers
public delegate void aGraboidHit();
public delegate void aGraboidDied();
public delegate void ability(); // An ability that is self-contained/affects the self
public delegate void abilityO(Player opponent); // An ability that needs a target opponent designated
// Generic Character template; allows pointing to fire function, and menu options generation
public abstract class Character {
public abstract void fire(Player p, int[] givenC);
public abstract void profile(Player p);
// Specific characters' profiles should start with their name, then list abilities
// and label whether each is a passive ability, or an active with X uses left
public ability ability1; // The numbers are constant, maintain #s per slot: 1st abil, 2nd abil
public ability ability2; // The "ability" delegate is for no-parameter abilities
public abilityO abilityO1; // The "abilityO"'s are optional, in case the ability (1st
public abilityO abilityO2; // or 2nd slot) targets an Opponent as a parameter
public List<menuItem> menuOptions = new List<menuItem>();
public event ability graboidHit;
public event ability graboidDied;
public void getHit() {
// If there's no subscribers to graboidHit, then there's no reason to fire it (causes errors)
if(graboidHit != null) {
graboidHit();
}
}
public void getKilled() {
// If there's no subscribers to graboidDied, then there's no reason to fire it (causes errors)
if(graboidDied != null) {
graboidDied();
}
}
// Show options
/* Displays available options for the character, and prompts for input. Accepts a default input of X#
* format and directly passes to fire, else executes the chosen method, returns whether or not the turn
* should continue based on the action taken, and reloads the menu options if the turn is not over. */
public virtual bool options(Player opponent) {
Console.WriteLine("Enter coordinates to fire on, or choose an option below:");
for(int i = 0; i < menuOptions.Count; i++) {
Console.WriteLine("{0}) {1}", i + 1, menuOptions[i].label);
}
Console.Write("Command >> ");
bool success = false;
int input = 0;
string rawInput = Console.ReadLine();
if(rawInput.Length == 2) {
// Got 2 chars? Fire
int[] xy = ANtoXY(rawInput);
while(xy[0] == -1) {
xy = getCoords();
}
fire(opponent, xy);
return true;
} else if(rawInput.Length == 1) {
// Got 1 char? Do that menu option
success = int.TryParse(rawInput, out input);
// If they enter something outside of the # of options, repeat turn
if(input < 1 || input > menuOptions.Count) {
success = false;
}
}
if(!success) {
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Unrecognized input.");
Console.ResetColor();
return false;
}
// If they didn't fire, adjust the input and execute the chosen menuOption
input--;
// Need to record whether or not this ability will end the turn, BEFORE executing the ability
bool done = menuOptions[input].endsTurn;
/* WHY: If an ability runs out of uses, it removes itself from the menu as an option for
* the future. This makes menuOptions[input] point to the NEXT menu option down, and
* then read "did the method end the turn?" from THAT option, which can allow a
* character to get an extra turn upon depleting an active ability. */
menuOptions[input].method();
return done;
}
public void disableOption(string label, string msg) {
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(msg);
Console.ResetColor();
menuOptions.Remove(menuOptions.Single(o => o.label == label));
}
}
// -- Specific Character classes
// No Character
public class noCharacter : Character {
public override void fire(Player p, int[] givenC) {
basicFire(p, givenC);
}
public override void profile(Player p) {
Console.Write("Critical, NEED TO KNOW information: ");
p.writeName();
Console.Write(" isn't using a character, smart guy.\n"
+ "a.k.a. Think of me as regular Battleship by Hasbro (née Milton Bradley)");
}
}
// Valentine McKee
public class valentineMcKee : Character {
private int rage = 0;
private int deadGraboids = 0;
private int shots = 0;
public override void fire(Player p, int[] givenC) {
// Fire until spent
while(shots > 0 && !gameOver) {
basicFire(p, givenC);
if(givenC != null) { givenC = null; }
shots--;
if(shots > 0 && !gameOver) {
Console.ForegroundColor = ConsoleColor.Magenta;
Console.Write("\nRAGE! ");
Console.ResetColor();
p.showBoard();
Console.ForegroundColor = ConsoleColor.Magenta;
Console.Write("{0} shot(s) remaining. ", shots);
Console.ResetColor();
}
}
// Reset rage counter
rage = 0;
}
public override bool options(Player opponent) {
// Calculate shots
shots = deadGraboids;
if(shots < 1) {
shots = 1;
}
shots += rage;
if(shots > 1) {
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("There's enough rage in you for {0} shots this round.", shots);
Console.ResetColor();
}
bool done = base.options(opponent);
return done;
}
// Constructor
public valentineMcKee() {
ability1 = new ability(dontGDpushMe);
ability2 = new ability(fYou);
}
// Ability 1 - Passive - Don't You GD Push Me
public void dontGDpushMe() {
deadGraboids++;
// Console.WriteLine("Dead graboids {0}", deadGraboids);
}
// Ability 2 - Passive - Fuuuuu YOU
public void fYou() {
this.rage++;
// Console.WriteLine("Rage {0}", rage);
}
public override void profile(Player p) {
Console.Write("Critical, NEED TO KNOW information about ");
Console.BackgroundColor = p.myColor;
Console.Write("Valentine McKee");
Console.ResetColor();
Console.WriteLine(":\n\n"
+ "Valentine McKee is a drifter with a foul mouth, just trying to scrape by taking any work he can\n"
+ "get his hands on. Known for his short temper and reactionary demeanor, few people cross Val if\n"
+ "they don't have to.");
Console.WriteLine("\nAbilities:\n");
int estShots = deadGraboids;
if(estShots < 1) { estShots = 1; }
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("Don't You GD Push Me (Passive)");
Console.ResetColor();
Console.WriteLine("Each turn, Val fires as many times as he has dead graboids, with a minimum of 1.\n" +
"[Current base fire: {0} shot(s)]", estShots);
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("Fuuuuu-- YOU! (Passive)");
Console.ResetColor();
Console.WriteLine("Val fires an additional shot for every hit his graboids suffered in the previous turn."
+ "\n[Current rage bonus: {0} additional shot(s)]", rage);
Console.WriteLine();
}
}
// Earl Bassett
public class earlBassett : Character {
public override void fire(Player p, int[] givenC) {
basicFire(p, givenC);
}
public override bool options(Player opponent) {
// Check for seizmojigger located guaranteed hit
if(seizmoLocated != null) {
string[] targetStatus = target(opponent.board, seizmoLocated);
// If the location has already been hit, nevermind and clear located
if(targetStatus[0] != null) {
seizmoLocated = null;
} else {
// Otherwise, display the location
char a = (char) (seizmoLocated[0] + 65);
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("The seizmojigger has located a graboid at {0}{1}! ", a, seizmoLocated[1]);
Console.ResetColor();
}
}
bool done = base.options(opponent);
return done;
}
// Constructor
public earlBassett() {
abilityO1 = new abilityO(rcBigfootBomb);
abilityO2 = new abilityO(seizmojigger);
}
// Ability 1 - Active - R/C Bigfoot Bomb
private int rcBombs = 3;
private int rcSteps = 6;
public void rcBigfootBomb(Player p) {
// Initiate steps & recursive subroutine, preset to no previous coords (indicated by [-1,-1])
rcShot(p, new int[] { -1, -1 }, rcSteps);
// Decrement R/C Bombs, and disable ability if it's used up
rcBombs--;
if(rcBombs == 0) {
disableOption("R/C Bigfoot Bomb", "No more R/C bombs!");
}
}
// R/C Bigfoot Bomb, part 2, recursive routine to choose adjacent coordinates
public void rcShot(Player p, int[] prev, int steps) {
int[][] adj = getAdjCoords(prev); // Coordinates adjacent to previous shot (if any, else null)
int[] c; // Current coordinates chosen
// If this was passed prev coords
if(adj != null) {
bool validTargetsInAdj = false;
foreach(int[] v in adj) {
// If just one coord in adj is in grid and not fired on, valid targets exist
string[] untried = target(p.board, v);
if(untried[0] == null) {
validTargetsInAdj = true;
break;
}
}
// If there are no valid targets, quit
if(!validTargetsInAdj) {
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Nowhere for R/C to run to! Self-destructing!");
Console.ResetColor();
return;
}
}
// Ask for new coordinates
bool OK = false;
do {
Console.ForegroundColor = ConsoleColor.Magenta;
Console.Write("R/C Bigfoot Bomb");
Console.ResetColor();
Console.Write(" ({0} shots remaining): ", steps);
c = getCoords();
// If there was a prev shot, be sure new coords are in our valid target set around it
if(adj == null) {
OK = true;
} else {
foreach(int[] v in adj) {
if(c.SequenceEqual(v)) {
OK = true;
break;
}
}
}
// Alert reminder if you weren't adjacent
if(!OK) {
char a = (char) (prev[0] + 65);
Console.WriteLine("You must select coordinates adjacent to {0}{1}.", a, prev[1]);
}
} while(!OK);
// Fire on new coords
string[] targetStatus = target(p.board, c);
// This part is copied from basicFire, but recurs rcShot if the space was already fired on or misses
// On state:null, check for graboid and hit or miss it; On state:hit/miss, cancel
switch(targetStatus[0]) {
case null:
// On graboid present, hit; else, miss
if(targetStatus[1] != null) {
p.board.grid[c[0], c[1]].state = "hit";
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("\nHit!!");
Console.ResetColor();
// Prod player to broadcast a hit event
p.character.getHit();
p.dmgGraboid(targetStatus[1]);
return;
} else {
p.board.grid[c[0], c[1]].state = "miss";
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("\nMiss...");
Console.ResetColor();
// Steps down, and if it's out, end the recursion and R/C Bomb
steps--;
if(steps == 0) {
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("R/C ran out of juice...");
Console.ResetColor();
return;
}
// Otherwise, take the next step, using the current coords as prev
rcShot(p, c, steps);
return;
}
default:
// Convert XY to AN for screen printing purposes
char a = (char) (c[0] + 65);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("You've already fired on {0}{1}, it was a {2}.",
a, c[1], targetStatus[0]);
Console.ResetColor();
rcShot(p, prev, steps);
return;
}
}
// Ability 2 - Active - Seizmojigger
private int seizmo = 2;
private int[] seizmoLocated = null;
public void seizmojigger(Player p) {
// Don't let the player use Seizmojigger twice in a row, they'll lose the previous located coords
if(seizmoLocated != null) {
char a = (char) (seizmoLocated[0] + 65);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("The seizmojigger cannot be used again until you fire on {0}{1}.", a, seizmoLocated[1]);
Console.ResetColor();
fire(p, null);
return;
}
// Alert that the opponent handles this, then "end turn" (visually, in code the turn is still on)
Console.ForegroundColor = ConsoleColor.Magenta;
Console.Write("Seizmojigger! ");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("Your turn is over, ");
p.writeName();
Console.WriteLine(" will reveal a graboid's coordinates on their turn.");
Console.ResetColor();
// Decrement sprays while player is still viewing screen, and disable ability if it's used up
seizmo--;
if(seizmo == 0) {
disableOption("Seizmojigger", "The seizmojigger's busted!");
}
Console.Write("\nTurn Over. ");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("Please LOOK AWAY from the screen and give control to ");
p.writeName();
Console.Write("! ");
Console.ResetColor();
Console.Write("Press any key to continue...");
Console.ReadKey();
Console.Clear();
// Opponent gains control
p.writeName();
Console.Write(", your opponent used ");
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("Seizmojigger. ");
Console.ResetColor();
Console.WriteLine("You must reveal a coordinate that contains a graboid which has not yet been hit " +
"to your opponent.\n");
Console.WriteLine("Select coordinates which will hit, in the format X#.");
// Extracting to separate process to allow retries in case of an error
opponentDisclosesSeizmojigger(p);
}
// Seizmojigger, part 2, opponent's responsibilities
public void opponentDisclosesSeizmojigger(Player p) {
// -- Collect input from user
int[] disclosed = new int[2];
p.showBoard('g');
Console.Write("Disclose: ");
disclosed = getCoords();
// -- Validate
int errors = 0;
// Confirm that coords are an untried hit
string[] untriedHit = target(p.board, disclosed);
if(untriedHit[1] == null) {
Console.WriteLine("Coordinates are not occupied by a graboid and would be a miss.");
errors++;
} else if(untriedHit[0] != null) {
Console.WriteLine("Coordinates already fired on!");
errors++;
}
// Prompt to try again if there were errors
if(errors > 0) {
Console.WriteLine("Please re-select coordinates.");
opponentDisclosesSeizmojigger(p);
return;
}
// -- Finalize
// Write validated, confirmed set of coordinates to seizmo's location
seizmoLocated = disclosed;
Console.WriteLine("Seizmojigger has located a graboid. Thank you!");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("Standby, ");
p.writeName();
Console.Write("! ");
Console.ResetColor();
Console.WriteLine("You will be taking your turn next...");
return;
}
public override void profile(Player p) {
Console.Write("Critical, NEED TO KNOW information about ");
Console.BackgroundColor = p.myColor;
Console.Write("Earl Bassett");
Console.ResetColor();
Console.WriteLine(":\n\n"
+ "Earl Bassett is a hired hand, part time repairman, and seasonal ostrich ranch hand, turned reluctant\n"
+ "graboid exterminator. Cautious and calculating, Earl doesn't like to take chances - which has occasionally\n"
+ "lead to him missing some of his biggest opportunities.");
Console.WriteLine("\nAbilities:\n");
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("R/C Bigfoot Bomb ({0} of 3 left)", rcBombs);
Console.ResetColor();
Console.WriteLine("Earl remote controls a toy 4x4 with dynamite strapped to it, which fires one normal\n" +
"shot, and if it misses, allows Earl to fire again in an adjacent coordinate. Earl continues to try\n" +
"again until a total of {0} shots have been attempted.", rcSteps);
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("Seizmojigger ({0} of 2 left)", seizmo);
Console.ResetColor();
Console.WriteLine(
"Earl activates the seizmojigger sonar system, passing his turn and forcing the opponent to reveal\n" +
"a coordinate that has not yet been fired on, which will result in a hit for Earl. This space will be\n" +
"revealed to Earl the next time he chooses to Fire.");
Console.WriteLine();
}
}
// Burt Gummer
public class burtGummer : Character {
public override void fire(Player p, int[] givenC) {
extraAmmo();
do {
basicFire(p, givenC);
if(givenC != null) { givenC = null; }
ammo--;
if(ammo > 0 && !gameOver) {
Console.ForegroundColor = ConsoleColor.Magenta;
Console.Write("\nExtra Ammo! ");
Console.ResetColor();
p.showBoard();
}
} while(ammo > 0 && !gameOver);
}
// Constructor
public burtGummer() {
ability1 = new ability(extraAmmo);
abilityO2 = new abilityO(clusterCharge);
}
// Ability 1 - Passive - Extra Ammo
private int ammo = 0;
public void extraAmmo() {
this.ammo++;
}
// Ability 2 - Active - Cluster Charge
private int charges = 2;
public void clusterCharge(Player p) {
Console.ForegroundColor = ConsoleColor.Magenta;
Console.Write("Cluster Charge: ");
Console.ResetColor();
// Get center shot
int[] c = getCoords();
string[] targetStatus = target(p.board, c);
// Copy pasted from basicFire, but recurs Cluster Charge if fired on an already fired space
// On state:null, check for graboid and hit or miss it; On state:hit/miss, cancel
switch(targetStatus[0]) {
case null:
// On graboid present, hit; else, miss
if(targetStatus[1] != null) {
p.board.grid[c[0], c[1]].state = "hit";
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("\nHit!!");
Console.ResetColor();
p.character.graboidHit -= ability1; // Turn off Extra Ammo, Cluster Charge shouldn't trigger
// Prod player to broadcast a hit event (for anyone else listening for other reasons)
p.character.getHit();
p.dmgGraboid(targetStatus[1]);
} else {
p.board.grid[c[0], c[1]].state = "miss";
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("\nMiss...");
Console.ResetColor();
p.character.graboidHit -= ability1; // Turn off Extra Ammo, Cluster Charge shouldn't trigger
// This handles accidental triggers in the surrounding coords
}
break;
default:
// Convert XY to AN for screen printing purposes
char a = (char) (c[0] + 65);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("You've already fired on {0}{1}, it was a {2}.",
a, c[1], targetStatus[0]);
Console.ResetColor();
clusterCharge(p);
return;
}
// Fire on all adjacent coordinates
int[][] adj = getAdjCoords(c);
foreach(int[] cn in adj) {
char a = (char) (cn[0] + 65);
Console.Write("Cluster Charge bursts damage to {0}{1}: ", a, cn[1]);
string[] cnStatus = target(p.board, cn);
// Copy pasted from basicFire, without recursion from firing on previous coordinates
// On state:null, check for graboid and hit or miss it; On state:hit/miss, cancel
switch(cnStatus[0]) {
case null:
// On graboid present, hit; else, miss
if(cnStatus[1] != null) {
p.board.grid[cn[0], cn[1]].state = "hit";
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("\nHit!!");
Console.ResetColor();
// Prod player to broadcast a hit event
p.character.getHit();
p.dmgGraboid(cnStatus[1]);
} else {
p.board.grid[cn[0], cn[1]].state = "miss";
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("\nMiss...");
Console.ResetColor();
}
break;
default:
// Convert XY to AN for screen printing purposes
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("\nYou've already fired on {0}{1}, it was a {2}.", a, cn[1], cnStatus[0]);
Console.ResetColor();
break;
}
}
p.character.graboidHit += ability1; // Turn Extra Ammo listener back on
// Decrement charges, and disable ability if it's used up
charges--;
if(charges == 0) {
disableOption("Cluster Charge", "Out of Cluster Charges!");
}
}
public override void profile(Player p) {
Console.Write("Critical, NEED TO KNOW information about ");
Console.BackgroundColor = p.myColor;
Console.Write("Burt Gummer");
Console.ResetColor();
Console.WriteLine(":\n\n"
+ "Burt Gummer is a firearms enthusiast, and a paranoid survivalist, who has only run out of ammo\n"
+ "*once*. He has an \"overkill\" approach to problem solving and takes himself deadly seriously,\n"
+ "much to the annoyance and/or amusement of others.");