#include <vectrex.h>
// Added one sound effect to Beta0.3 - no other changes.
struct sound_adsr_table_t
{
int amplitude[16]; // 32 nibbles amplitude offsets
};
struct sound_twang_table_t
{
int frequency[8]; // base frequency offsets
};
struct sound_explosion_t
{
int enable; // noise (bits 5-3) and tone channel (bits 2-0) enable (0b00nnnccc),
int noise; // noise source sweep, =0 up, >0 down, <0 inhibit
int volume; // volume sweep, =0 up, >0 down, <0 inhibit
unsigned int duration; // explosion duration, 0x01 longest to 0x80 shortest
};
#define SOUND_EXPL_RISE 0
#define SOUND_EXPL_FALL 1
#define SOUND_EXPL_EVEN -1
#define SOUND_VOL_RISE 0
#define SOUND_VOL_FALL 1
#define SOUND_VOL_EVEN -1
void play_explosion(const struct sound_explosion_t* explosion);
void stop_explosion();
const struct sound_explosion_t explosion_type_0 =
{
0b00101010, SOUND_EXPL_RISE, SOUND_VOL_RISE, 1U
};
const struct sound_explosion_t explosion_type_1 =
{
0b00000111, SOUND_EXPL_EVEN, SOUND_VOL_RISE, 3U
};
const struct sound_explosion_t explosion_off =
{
0b00111111, SOUND_EXPL_RISE, SOUND_VOL_FALL, 128U
};
const struct sound_explosion_t* current_explosion = &explosion_off;
void sound_init(void)
{
current_explosion = &explosion_off;
}
void stop_explosion(void)
{
Vec_Expl_Timer = 0;
Vec_Expl_Flag = 0;
current_explosion = &explosion_off;
}
void play_explosion(const struct sound_explosion_t* explosion)
{
// Vec_Expl_Timer == 0 indicates that no current explosion is playing
Stop_Sound();
current_explosion = explosion;
Vec_Expl_Flag = 128; // start new explosion
}
#define pot0 (*((volatile int *) 0xc81b))
#define pot1 (*((volatile int *) 0xc81c))
int XJoy = 0, YJoy = 0; // convert analog value to digital -1 0 +1
unsigned int _x, _a, _b, _c;
void initRandom(unsigned int s1,unsigned int s2,unsigned int s3, unsigned int x0) {
_x = x0; _a = s1; _b = s2; _c = s3; _x++; _a = (_a^_c^_x); _b = (_b+_a); _c = ((_c+(_b>>1))^_a);
}
int irand127(void) { // assert returns unsigned value that fits in an int.
_x++; _a = (_a^_c^_x); _b = (_b+_a); _c = ((_c+(_b>>1))^_a); return (int)(_c&127);
}
int debug = 0;
// Note that machine originally appeared to play 'O' even if it played first.
// Was able to fix this in the board display by noting who played first, and
// swapping X for O accordingly on output.
#ifndef FALSE
#define FALSE (0 != 0)
#define TRUE (0 == 0)
#endif
// In the original BASIC code, a square was marked as 1 for a human play
// and 5 for a computer play, and 1/8 for a potential computer play. That way,
// adding up the fractional parts over 4 squares could never reach 1 and so
// the fractional part could be ignored. Since we are using integers, the
// easiest way to handle this awkward coding was to first separate the relevant
// variables into separate integer and fractional parts, and finally convert
// the fractional parts into a more simple boolean array of flags.
int X[64+1]; // X is the 3d array, flattened. empty=0
#define HUMAN_PLAY 1
#define COMPUTER_PLAY 5
int Marked[64+1];
#define MARKED 1
#define UNMARKED 0
#define Y(I) y[(I)-1]
const int y[16] = { 1,49,52,4,13,61,64,16,22,39,23,38,26,42,27,43, };
#define ALL_POSSIBLE_LINES 76
#define M(I,J) m[(I)-1][(J)-1]
int Winning_Line = 0;
const int m[ALL_POSSIBLE_LINES][4] = { // winning lines
{ 1,2,3,4}, { 5,6,7,8}, { 9,10,11,12}, { 13,14,15,16}, // <-- fourth column may have special significance in FIND_MOVE
{ 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},
{ 1,17,33,49}, { 5,21,37,53}, { 9,25,41,57}, { 13,29,45,61},
{ 2,18,34,50}, { 6,22,38,54}, { 10,26,42,58}, { 14,30,46,62},
{ 3,19,35,51}, { 7,23,39,55}, { 11,27,43,59}, { 15,31,47,63},
{ 4,20,36,52}, { 8,24,40,56}, { 12,28,44,60}, { 16,32,48,64},
{ 1,5,9,13}, { 17,21,25,29}, { 33,37,41,45}, { 49,53,57,61},
{ 2,6,10,14}, { 18,22,26,30}, { 34,38,42,46}, { 50,54,58,62},
{ 3,7,11,15}, { 19,23,27,31}, { 35,39,43,47}, { 51,55,59,63},
{ 4,8,12,16}, { 20,24,28,32}, { 36,40,44,48}, { 52,56,60,64},
{ 1,6,11,16}, { 17,22,27,32}, { 33,38,43,48}, { 49,54,59,64},
{ 13,10,7,4}, { 29,26,23,20}, { 45,42,39,36}, { 61,58,55,52},
{ 1,21,41,61}, { 2,22,42,62}, { 3,23,43,63}, { 4,24,44,64},
{ 49,37,25,13}, { 50,38,26,14}, { 51,39,27,15}, { 52,40,28,16},
{ 1,18,35,52}, { 5,22,39,56}, { 9,26,43,60}, { 13,30,47,64},
{ 49,34,19,4}, { 53,38,23,8}, { 57,42,27,12}, { 61,46,31,16},
{ 1,22,43,64}, { 16,27,38,49}, { 4,23,42,61}, { 13,26,39,52},
};
#define animation_delay 6
#define status_delay 255U
#define lockout_delay 255U
unsigned int status_ticker = 0U;
static char status[32];
void STATUS(char *s) {
int i;
status_ticker = status_delay;
for (i = 0; i < 30; i++) {
status[i] = s[i];
if (s[i] == '\0') {
status[i++] = 0x80; status[i] = 0; return;
}
};
status[30] = '\x80'; status[31] = '\0';
}
int xyz(int x, int y, int z) {
x -= 1; y -= 1; z -= 1;
return 1 + (z*16 + y*4 + x);
}
void swap(int x1, int y1, int z1, int x2, int y2, int z2) {
int p1, p2, temp;
p1 = xyz(x1, y1, z1); p2 = xyz(x2, y2, z2);
temp = X[p1]; X[p1] = X[p2]; X[p2] = temp;
}
#define Lineto_d(y,x) Draw_Line_d(y,x)
#define MoveRel(x,y) Moveto_d(y, x)
#define LineRel(x,y) Draw_Line_d(y, x)
#define set_scale(s) do { VIA_t1_cnt_lo = s; } while (0)
// perhaps this should be rewritten. Currently drawing an X or an O involves 3 different
// scaling values. First we position the level, then the square within the level, and
// finally the letter itself.
// I manually worked out a formula for going directly to the square independent of level, which
// I used in the "Join" procedure to draw a (graphical) line through a winning line. Using
// that same formula here would eliminate one of the vector moves. The only question is whether
// the letters would be placed as perfectly within the boxes as they currently are, or whether
// they would be a smidgeon off center depending on level (due to coarseness of the 256x256 grid)
static inline void draw_X(int x, int y, int z) {
z -= 3; x -= 1; y -= 1; y *= 30;
Reset0Ref(); set_scale(0xf0); Moveto_d(z*40+3, -44);
set_scale(0x41);
MoveRel((x<<5)-x, y);MoveRel(y-3, 0);
set_scale(0x10); LineRel(96, 40); MoveRel(-40,-40); LineRel(-16, 40);
}
static inline void draw_O(int x, int y, int z) {
z -= 3; x -= 1; y -= 1; y *= 30;
Reset0Ref(); set_scale(0xf0); Moveto_d(z*40+3, -44);
set_scale(0x41); MoveRel((x<<5)-x, y);MoveRel(y, 0);
set_scale(0x0c); LineRel(16, 32); LineRel(80, 32); LineRel(-16, -32); LineRel(-80, -32);
}
void DrawGrid(int z) {
z -= 3;
Reset0Ref(); set_scale(0xf0); Moveto_d(z*40, -50);
set_scale(0x40);
Lineto_d(124, 124);
Moveto_d(0, 31); Lineto_d(-124, -124);
Moveto_d(0, 31); Lineto_d(124, 124);
Moveto_d(0, 31); Lineto_d(-124, -124);
Moveto_d(0, 31); Lineto_d(124, 124);
Reset0Ref(); set_scale(0xf0); Moveto_d(z*40, -50);
set_scale(0x40);
Lineto_d(0, 124);
Moveto_d(31, 31); Lineto_d(0, -124);
Moveto_d(31, 31); Lineto_d(0, 124);
Moveto_d(31, 31); Lineto_d(0, -124);
Moveto_d(31, 31); Lineto_d(0, 124);
}
#define MACHINE 0
#define HUMAN 1
static int first_player = HUMAN;
// If you would rather ask on first time, then alternate, set this to MACHINE-HUMAN
static int Move, RandBase, R, I, J, SumOfPlays, SumOfMarks, K;
static void PRINT_MACHINE_MOVE(char *message, int M) {
int i, K1, K2, K3, J2;
K1=((M-1)>>4) + 1; // Z
J2=M-((K1-1)*16);
K2=((J2-1)>>2) + 1; // Y
K3=M-((K1-1)*16)-((K2-1)*4); // X
status_ticker = status_delay;
for (i = 0; i < 27; i++) {
status[i] = message[i];
if (message[i] == '\0') {
status[i++] = (char)(K3+'0');
status[i++] = (char)(K2+'0');
status[i++] = (char)(K1+'0');
status[i++] = 0x80; status[i] = 0; return;
}
};
status[30] = '\x80'; status[31] = '\0';
}
int FIND_MOVE(int I, int mark) { // mark is 1 first time called, then 0.
if ((((I-1)&3) == 3) || (((I-1)&3) == 0)) {
// I suspect that placing corner squares is handled differently,
// and blocking options will never be in the corners.
for (J=2; J <= 3; J+=1) {
Move=M(I,J);
if ((X[Move] == 0) && (Marked[Move] == mark)) {
X[Move]=COMPUTER_PLAY;
PRINT_MACHINE_MOVE("I TAKE ", Move);
return HUMAN; // tell caller to "goto HUMAN_MOVE"...
}
}
} else {
for (J=1; J <= 4; J+=3) {
Move=M(I,J);
if ((X[Move] == 0) && (Marked[Move] == mark)) {
X[Move]=COMPUTER_PLAY;
PRINT_MACHINE_MOVE("I TAKE ", Move);
return HUMAN; // tell caller to "goto HUMAN_MOVE"...
}
}
}
return 0;
}
// state machine:
#define COMPUTER_MOVE_WANTED 1
#define HUMAN_MOVE_WANTED 2
#define PLAY_AGAIN_WANTED 3
// user input:
#define FLIP_REQUESTED 101
#define FIRST_MENU_ITEM FLIP_REQUESTED
#define SCRAMBLE_REQUESTED 102
#define INVERT_REQUESTED 103
#define ROTATE_REQUESTED 104
#define TUMBLE_REQUESTED 105
#define WHIRL_REQUESTED 106
//#define HELP_REQUESTED 107 ... too much to do right now...
#define QUIT_REQUESTED 107
#define LAST_MENU_ITEM QUIT_REQUESTED
static int highlighted_menu_item = ROTATE_REQUESTED;
void DrawMenu(int selected) {
Intensity_5F();
if (selected == FLIP_REQUESTED) Intensity_7F();
Print_Str_d(-60, 50, "FLIP\x80");
if (selected == FLIP_REQUESTED) Intensity_5F();
if (selected == SCRAMBLE_REQUESTED) Intensity_7F();
Print_Str_d(-40, 50, "SCRAMBLE\x80");
if (selected == SCRAMBLE_REQUESTED) Intensity_5F();
if (selected == INVERT_REQUESTED) Intensity_7F();
Print_Str_d(-20, 50, "INVERT\x80");
if (selected == INVERT_REQUESTED) Intensity_5F();
if (selected == ROTATE_REQUESTED) Intensity_7F();
Print_Str_d(0, 50, "ROTATE\x80");
if (selected == ROTATE_REQUESTED) Intensity_5F();
if (selected == TUMBLE_REQUESTED) Intensity_7F();
Print_Str_d(20, 50, "TUMBLE\x80");
if (selected == TUMBLE_REQUESTED) Intensity_5F();
if (selected == WHIRL_REQUESTED) Intensity_7F();
Print_Str_d(40, 50, "WHIRL\x80");
if (selected == WHIRL_REQUESTED) Intensity_5F();
//if (selected == HELP_REQUESTED) Intensity_7F();
//Print_Str_d(60, 50, "HELP\x80");
//if (selected == HELP_REQUESTED) Intensity_5F();
if (selected == QUIT_REQUESTED) Intensity_7F();
Print_Str_d(80, 50, "QUIT\x80");
}
int check_human_input(int request) {
int x,y,z, temp[5][5][5];
if (request == FLIP_REQUESTED) {
int flip[5] = {0, 2,1,4,3};
for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) temp[x][y][z] = X[xyz(flip[x], flip[y], flip[z])];
for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) X[xyz(x, y, z)] = temp[x][y][z];
} else if (request == SCRAMBLE_REQUESTED) {
int scramble[5] = {0, 1,3,2,4};
for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) temp[x][y][z] = X[xyz(scramble[x], scramble[y], scramble[z])];
for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) X[xyz(x, y, z)] = temp[x][y][z];
} else if (request == INVERT_REQUESTED) { // mirror, top for bottom
for (z = 1; z <= 2; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) swap(x,y,z, x,y,5-z);
} else if (request == ROTATE_REQUESTED) { // clockwise round each face
for (z = 1; z <= 4; z++) {
for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) temp[x][y][z] = X[xyz(x, y, z)]; // z is unchanged
for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) X[xyz(x, y, z)] = temp[y][5-x][z];
}
} else if (request == TUMBLE_REQUESTED) { // Imagine a cube in front of you, now roll it farther away...
for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) temp[x][y][z] = X[xyz(x, y, z)];
for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) X[xyz(x, y, z)] = temp[x][5-z][y]; // x is unchanged
} else if (request == WHIRL_REQUESTED) { // Imagine a cube in front of you, now roll it to the right...
for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) temp[x][y][z] = X[xyz(x, y, z)];
for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) X[xyz(x, y, z)] = temp[5-z][y][x]; // y is unchanged
//} else if (request == HELP_REQUESTED) {
} else if (request == QUIT_REQUESTED) {
STATUS("YOU RESIGN!"); // wipe table immediately or leave it for a few secs?
return PLAY_AGAIN_WANTED;
}
return HUMAN_MOVE_WANTED; // because nothing significant could be done (yet)
}
int computer_move(void) {
for (I=1; I <= 64; I++) Marked[I]=UNMARKED; // Reset temporary markers before computer evaluation
//if (debug) fprintf(stdout, "Check to see if player has won (4 X's)\n");
for (I=1; I <= ALL_POSSIBLE_LINES; I++) {
SumOfPlays=X[M(I,1)]+X[M(I,2)]+X[M(I,3)]+X[M(I,4)]; // Sum the X's and O's in each line.
// SumOfPlays now holds the score for every line
if (SumOfPlays == 4) { // Human has 4 pieces in a line!
Winning_Line = I;
STATUS("YOU WIN!");
//first_player = 1-first_player; // these can all be moved to the 'play again' code
return PLAY_AGAIN_WANTED;
}
}
//if (debug) fprintf(stdout, "Check for forced win (3 O's and an empty 4th space)\n");
for (I=1; I <= ALL_POSSIBLE_LINES; I++) {
SumOfPlays=X[M(I,1)]+X[M(I,2)]+X[M(I,3)]+X[M(I,4)]; // Sum the X's and O's in each line.
// SumOfPlays now holds the score for every line
if (SumOfPlays == 15) { // Computer has 3 pieces in a line.
for (J=1; J <= 4; J++) {
Move=M(I,J);
if (X[Move] == 0) {
X[Move]=COMPUTER_PLAY; // place computer's mark on the only remaining open position to win
PRINT_MACHINE_MOVE("I MOVE TO ", Move); // suppress redundant board display
// change to MACHINE WINS WITH ... and remove status below, because
// unless we queue messages, this one will be imediately overwritten...
}
}
Winning_Line = I;
STATUS("I WIN!"); // SET LOCKOUT? OR IS IT DONE ELSEWHERE?
//first_player = 1-first_player;
return PLAY_AGAIN_WANTED;
}
}
//if (debug) fprintf(stdout, "Human has placed 3 X's in a row, so computer blocks 4th position...\n");
for (I=1; I <= ALL_POSSIBLE_LINES; I++) {
SumOfPlays=X[M(I,1)]+X[M(I,2)]+X[M(I,3)]+X[M(I,4)]; // Sum the X's and O's in each line.
// SumOfPlays now holds the score for every line
if (SumOfPlays == 3) { // human almost has a line, so computer has to block it...
for (J=1; J <= 4; J++) {
Move=M(I,J);
if (X[Move] == 0) {
X[Move]=COMPUTER_PLAY;
PRINT_MACHINE_MOVE("NICE! I MOVE TO ", Move);
return HUMAN_MOVE_WANTED;
}
}
break; // WHY IS THIS A BREAK??? may be correct but need to check...
}
}
// nothing was forced, so can we create a forcing move?
//if (debug) fprintf(stdout, "Create forcing move?\n");
RandBase = (int)irand127();
for (R=0; R < ALL_POSSIBLE_LINES; R++) { I = (int)((((unsigned)R + (unsigned)RandBase) % (unsigned)ALL_POSSIBLE_LINES) + 1U); // Shuffle order of scan
// I = ((R + RandBase) % ALL_POSSIBLE_LINES) + 1; // Shuffle order of scan
SumOfPlays=X[M(I,1)]+X[M(I,2)]+X[M(I,3)]+X[M(I,4)];
SumOfMarks=Marked[M(I,1)]+Marked[M(I,2)]+Marked[M(I,3)]+Marked[M(I,4)];
if (SumOfPlays < 10) continue; // Only one of computer's
if (SumOfPlays >= 11) continue; // Two of computer's and one of human's - cannot make this line
if ((SumOfPlays == 10) && (SumOfMarks > 0)) { // Two of computer's, plus one of the spare squares is already marked as being useful for another line
for (J=1; J <= 4; J++) {
Move=M(I,J);
if ((X[Move] == 0) && (Marked[Move] == MARKED)) {
X[Move]=COMPUTER_PLAY;
// PROBLEM!!! SOME STRINGS ARE TOO LONG!!!!!!
PRINT_MACHINE_MOVE("SMART MOVE IS ", Move);
return HUMAN_MOVE_WANTED;
}
}
STATUS("I CONCEDE.");
//first_player = 1-first_player;
return PLAY_AGAIN_WANTED;
}
// this part is executed if SumOfPlays == 10, i.e. if the computer has placed two O's,
// mark the remaining 2 free positions as potential plays (marker is '1/8')
// If the computer plays in a square that is marked, it means that the computer
// has created two different lines that have three squares with one free, i.e.
// can win on next turn unless human wins with next move...
for (J=1; J <= 4; J++) if (X[M(I,J)] == 0) Marked[M(I,J)]=MARKED; // SQUARES ARE MARKED HERE
}
//if (debug) fprintf(stdout, "Four empty places or one of computer's marks and 3 empty places?\n");
// no great moves, so can we find a better-than-nothing move?
RandBase = irand127();
for (R=0; R < ALL_POSSIBLE_LINES; R++) { I = (int)((((unsigned)R + (unsigned)RandBase) % (unsigned)ALL_POSSIBLE_LINES) + 1U); // Shuffle order of scan
SumOfPlays=X[M(I,1)]+X[M(I,2)]+X[M(I,3)]+X[M(I,4)];
SumOfMarks=Marked[M(I,1)]+Marked[M(I,2)]+Marked[M(I,3)]+Marked[M(I,4)];
if ( (SumOfMarks == 4) || ((SumOfPlays == 5) && (SumOfMarks == 3)) ) { // four empty places or one of computer's marks and 3 empty places
if (FIND_MOVE(I, MARKED)) return HUMAN_MOVE_WANTED;
}
}
// Remove temporary markers?
for (I=1; I <= 64; I++) Marked[I]=UNMARKED;
//if (debug) fprintf(stdout, "Can I block human from creating a pin?\n");
RandBase = irand127();
for (R=0; R < ALL_POSSIBLE_LINES; R++) { I = (int)((((unsigned)R + (unsigned)RandBase) % (unsigned)ALL_POSSIBLE_LINES) + 1U); // Shuffle order of scan
SumOfPlays=X[M(I,1)]+X[M(I,2)]+X[M(I,3)]+X[M(I,4)];
SumOfMarks=Marked[M(I,1)]+Marked[M(I,2)]+Marked[M(I,3)]+Marked[M(I,4)];
if ((SumOfPlays < 2) || (SumOfPlays >= 3)) continue;
if ((SumOfPlays == 2) && (SumOfMarks > 0)) {
for (J=1; J <= 4; J++) {
Move=M(I,J);
if (Marked[Move] == MARKED) { // it is only marked if a previous line like this has also been examined. The first one is not.
X[Move]=COMPUTER_PLAY;
//if (debug) fprintf(stdout, "SumOfPlays=%d Marks=%d\n", SumOfPlays, SumOfMarks);
PRINT_MACHINE_MOVE("WHEW! I MOVE TO ", Move);
// I've seen this work well once and I've seen it pick a square that makes no sense once. Keep an eye on this...
return HUMAN_MOVE_WANTED;
}
}
// Can't block pin
STATUS("I CONCEDE.");
//first_player = 1-first_player;
return PLAY_AGAIN_WANTED;
}
for (J=1; J <= 4; J++) if (X[M(I,J)] == 0) Marked[M(I,J)]=MARKED; // SQUARES ARE MARKED HERE TOO!
}
//if (debug) fprintf(stdout, "Scan for 4 empty squares or 1 human play and 3 empty squares and a potential play marker...\n");
RandBase = irand127();
for (R=0; R < ALL_POSSIBLE_LINES; R++) { I = (int)((((unsigned)R + (unsigned)RandBase) % (unsigned)ALL_POSSIBLE_LINES) + 1U); // Shuffle order of scan
SumOfPlays=X[M(I,1)]+X[M(I,2)]+X[M(I,3)]+X[M(I,4)];
SumOfMarks=Marked[M(I,1)]+Marked[M(I,2)]+Marked[M(I,3)]+Marked[M(I,4)];
// SumOfPlays now holds the score for every line
if ((SumOfMarks == 4) || ((SumOfPlays == 1) && (SumOfMarks == 3))) { // 4 empty squares or 1 human play and 3 empty squares
if (FIND_MOVE(I, MARKED)) {
//if (debug) fprintf(stdout, "SumOfPlays=%d Marks=%d\n", SumOfPlays, SumOfMarks);
return HUMAN_MOVE_WANTED;
}
}
}
//if (debug) fprintf(stdout, "Scan over groups of 4 lines...\n");
RandBase = irand127();
for (R=0; R < 18; R++) { K = (int)((((unsigned)R + (unsigned)RandBase) % 18U) + 1U); // Shuffle order of scan
// K = ((R + RandBase) % 18) + 1;
// loop over 72 possible winning lines - not 76 ... maybe because last 4 are the 3D diagonals?
int P=0;
// adding up groups of 4 lines (e.g. 1,2,3,4, 5,6,7,8 ...) - perhaps trying to find planes that human is concentrating in?
for (I=4*K-3; I <= 4*K; I++) for (J=1; J <= 4; J++) P+=X[M(I,J)];
//if (debug) fprintf(stdout, "K%d P%d; ", K, P);
if (P<4) {
continue;
} else if (P<5) {
for (I=4*K-3; I < 4*K; I++) {
if (FIND_MOVE(I, MARKED)) {
//if (debug) fprintf(stdout, " [choice #1 P<5, marked]\n");
return HUMAN_MOVE_WANTED;
}
}
for (I=4*K-3; I < 4*K; I++) {
if (FIND_MOVE(I, UNMARKED)) {
//if (debug) fprintf(stdout, " [choice #2 P<5, unmarked]\n");
return HUMAN_MOVE_WANTED;
}
}
} else if (P<9) {
continue;
} else if (P<10) {
for (I=4*K-3; I < 4*K; I++) {
if (FIND_MOVE(I, MARKED)) {
//if (debug) fprintf(stdout, " [choice #3 P<10, marked]\n");
return HUMAN_MOVE_WANTED;
}
}
for (I=4*K-3; I < 4*K; I++) {
if (FIND_MOVE(I, UNMARKED)) {
//if (debug) fprintf(stdout, " [choice #4 P<10, unmarked]\n");
return HUMAN_MOVE_WANTED;
}
}
}
}
//if (debug) fprintf(stdout, "\n");
//if (debug) fprintf(stdout, "Place a corner square if any available?\n");
// Place a corner square
{int Z;
RandBase = irand127();
for (R=0; R < 16; R++) {Z = (int)((((unsigned)R + (unsigned)RandBase) % 16U) + 1U); // Shuffle order of scan
// Z = ((R + RandBase) % 16) + 1;
if (X[Y(Z)] == 0) { // Heuristic: are any of these positions free? 1,49,52,4,13,61,64,16,22,39,23,38,26,42,27,43
Move=Y(Z);
X[Move]=COMPUTER_PLAY;
PRINT_MACHINE_MOVE("I MOVE TO ", Move);
return HUMAN_MOVE_WANTED;
}
}
}
//if (debug) fprintf(stdout, "Otherwise just take next free square anywhere...\n");
for (I=1; I <= 64; I++) { // add the randbase tweak!
if (X[I] == 0) { // looks like it just choses first available space?
X[I]=COMPUTER_PLAY;
Move=I;
PRINT_MACHINE_MOVE("I LIKE ", Move);
return HUMAN_MOVE_WANTED;
}
}
//if (debug) fprintf(stdout, "But board is completely full!\n");
STATUS("IT'S A DRAW!");
return PLAY_AGAIN_WANTED;
}
// for pulsing flashing :-) (overkill)
static const long sine[65] =
{
0, 402, 803, 1205, 1605, 2005, 2404, 2801,
3196, 3589, 3980, 4369, 4756, 5139, 5519, 5896,
6269, 6639, 7005, 7366, 7723, 8075, 8423, 8765,
9102, 9434, 9759, 10079, 10393, 10701, 11002, 11297,
11585, 11866, 12139, 12406, 12665, 12916, 13159, 13395,
13622, 13842, 14053, 14255, 14449, 14634, 14810, 14978,
15136, 15286, 15426, 15557, 15678, 15790, 15892, 15985,
16069, 16142, 16206, 16260, 16305, 16339, 16364, 16379,
16384 // => 1.0
};
static long sinsymmetry(unsigned int angle128)
{
// '>', not '>=', is a special case for sin(x)=1.0
if (angle128 > 64U) return sine[128U-angle128]; else return sine[angle128];
}
static long fp2_14_sin(unsigned int angle256)
{
if ((unsigned char)angle256 >= 128U)
return -sinsymmetry((angle256-128U)&127U);
else
return sinsymmetry(angle256&127U);
}
void Join(int c1, int r1, int l1,
int c2, int r2, int l2) {
// x y z
// col row layer
// Using global coordinates, locate the center of any square
// (found by inspection)
// Y(row, layer) = -72+(layer-1)*38+(row-1)*8
// X(row, column, layer) = -39+(row-1)*8+(column-1)*8
//
int x1, y1, x2, y2;
if (((l1 == 4) && (l2 == 1))
|| ((l1 == 1) && (l2 == 4))) { // only endpoints on extreme levels (1 and 4) need special handling
// Long vector draws have to be split because > 127 units
// not sure if a coding error or a compiler bug, but the /3 version fails
// on some particular inputs :-(
int Dc = 0; //= (c1-c2)/3;
int Dr = 0; //= (r1-r2)/3; // Dr or Dc will be 0 if in the same plane.
int Dl = 0; //= (l1-l2)/3;
if (c2>c1) Dc = -1; else if (c2<c1) Dc = 1;
if (r2>r1) Dr = -1; else if (r2<r1) Dr = 1;
if (l2>l1) Dl = -1; else if (l2<l1) Dl = 1;
// A winning line from the computer is galling.
// Gallia est omnis divisa in partes tres
Join(c1, r1, l1, c1-Dc,r1-Dr,l1-Dl);
Join(c1-Dc,r1-Dr,l1-Dl, c2+Dc,r2+Dr,l2+Dl);
Join(c2+Dc,r2+Dr,l2+Dl, c2, r2, l2);
return;
}
Intensity_7F(); set_scale(0xff); Reset0Ref();
x1 = (r1+c1)*8-55; y1 = l1*38+r1*8-118;
x2 = (r2+c2)*8-55; y2 = l2*38+r2*8-118;
Moveto_d(y1,x1); Lineto_d(y2-y1,x2-x1);
}
int main(void)
{
//int sound_wanted = 0;
int whats_next;
int tick = 0;
unsigned int flash_timer = 0;
unsigned int lockout_timer = 0;
int HintLoc = -110;
unsigned long hint_timer = 0L;
#define HINT_DELAY 1000L
int buttons;
int row, column, layer;
// int debug_row = 1, debug_column = 1, debug_layer = 1;
// int debug_x = 0, debug_y = 0;
int player_x = 1, player_y = 1, player_z = 1;
int letter, idx; // x, y, z;
int step_x = 0, step_y = 0, step_z = 0, place_mark = 0;
int previous_step_x = 0, previous_step_y = 0, previous_step_z = 0, previous_place_mark = 0;
volatile unsigned int *rand = (volatile unsigned int *)0xc87b;
volatile unsigned int *timer_lo = (volatile unsigned int *)0xD004;
// unfortunately, on emulator at least, very first random is always the same,
// which means if computer plays first, it would always make the same play...
// so we always let the human play first!
initRandom(rand[0],rand[1],rand[2],*timer_lo);
for (I=1; I <= 64; I++) X[I]=0; // Init board
//if (first_player < 0) { // first time only...
first_player = HUMAN; // until logic is added to decide who's on first...
whats_next = HUMAN_MOVE_WANTED;
STATUS("YOU PLAY FIRST");
//}
sound_init();
for (;;) {
// poll joystick and buttons:
Joy_Digital();
XJoy = YJoy = 0;
if (pot0 < -10) XJoy = -1; // Left has no significance at the moment
else if (pot0 > 10) XJoy = 1; // Right means menu is displayed!
if (pot1 < -10) YJoy = -1; // Down - highlight selection if menu displayed
else if (pot1 > 10) YJoy = 1; // Up - ditto
asm("pshs x,y,u,d,dp");
asm("lda #0");
asm("jsr 0HF1B4 ; read_btns_mask");
asm("puls dp,d,u,y,x");
buttons = (*(volatile int *)(0xC80F));
// latch any buttons pressed any time during debounce timeout
if (buttons & 1) step_x = 1;
if (buttons & 2) step_y = 1;
if (buttons & 4) step_z = 1;
if (buttons & 8) place_mark = 1;
if ((!lockout_timer) && (whats_next == HUMAN_MOVE_WANTED)) {
if ((XJoy > 0) && place_mark && (!previous_place_mark)) {
// highlighted menu item is selected
previous_place_mark = place_mark;
whats_next = check_human_input(highlighted_menu_item);
}
// return values are:
// HUMAN_MOVE_WANTED (didn't get anything from the human that I can act on yet)
// PLAY_AGAIN_WANTED (human resigned)
}
if ((!lockout_timer) && (whats_next == COMPUTER_MOVE_WANTED)) {
whats_next = computer_move(); // might blank screen while computing
// return values are:
// HUMAN_MOVE_WANTED
// PLAY_AGAIN_WANTED
}
// now draw board...
DP_to_C8();
Explosion_Snd(current_explosion); // BIOS: management of explosions, leave out if you do not use explosions
Wait_Recal(); // sets dp to d0, and pos at 0, 0 No drawing above this line.
Do_Sound(); // BIOS: actual sound generation, compute and play current tunes
Intensity_3F();
// if a new game was requested, let's leave the old game on the screen for a short while
// to allow the human to see how he lost or to revel in his win...
if ((lockout_timer == 0) && (whats_next == PLAY_AGAIN_WANTED)) lockout_timer = lockout_delay;
if ((lockout_timer == 1) && (whats_next == PLAY_AGAIN_WANTED)) {
for (I=1; I <= 64; I++) X[I]=0; // Re-init board
first_player = 1-first_player;
hint_timer = 0L;
if (first_player == HUMAN) {
whats_next = HUMAN_MOVE_WANTED;
} else {
whats_next = COMPUTER_MOVE_WANTED;
}
}
// draw underlying board
for (layer = 1; layer <= 4; layer++) {
DrawGrid(layer);
}
Intensity_5F();
idx = 1;
for (layer = 1; layer <= 4; layer++) {
for (row = 1; row <= 4; row++) {
for (column = 1; column <= 4; column++) {
letter = X[idx++]; // xyz(column,row,layer)
if (first_player == HUMAN) {
if (letter == HUMAN_PLAY) {
draw_X(column,row,layer);
} else if (letter == COMPUTER_PLAY) {
draw_O(column,row,layer);
}
} else {
if (letter == COMPUTER_PLAY) {
draw_X(column,row,layer);
} else if (letter == HUMAN_PLAY) {
draw_O(column,row,layer);
}
}
}
}
}
Intensity_7F();
Reset0Ref();
if ((!lockout_timer) && (XJoy > 0)) {
DrawMenu(highlighted_menu_item);
}
if (!lockout_timer) {
if (hint_timer >= HINT_DELAY) { // re-do when board is compressed a little
if (!(XJoy > 0)) {
set_scale(0xdf);
Print_Str_d(HintLoc, -100, "USE THE BUTTONS:\x80"); // when it's the human's turn to play
Print_Str_d(HintLoc-17, -100, "X Y Z PLAY\x80"); // when it's the human's turn to play
}
}
}
Intensity_a(70+((((unsigned int)(fp2_14_sin(((unsigned int)flash_timer)&255U)>>9U))))); // this will be the human's piece that is to be played...
flash_timer += 4;
if ((whats_next == PLAY_AGAIN_WANTED) && Winning_Line) { // highlight the winning row
int K1, K2, K3, Q1, Q2, Q3, J2, M;
M=M(Winning_Line, 1);
letter=X[M];
K1=((M-1)>>4) + 1; // Z
J2=M-((K1-1)*16);
K2=((J2-1)>>2) + 1; // Y
K3=M-((K1-1)*16)-((K2-1)*4); // X
M=M(Winning_Line, 4);
letter=X[M];
Q1=((M-1)>>4) + 1; // Z
J2=M-((Q1-1)*16);
Q2=((J2-1)>>2) + 1; // Y
Q3=M-((Q1-1)*16)-((Q2-1)*4); // X
Join(K3,K2,K1, Q3,Q2,Q1); // draw a line through winning line
// SORT OF A BUG ... the Join above changes the intensity away from flashing,
// which messes up all the drawing below that should also be flashing, but
// since this is only called on a win, it's not really a serious problem...
for (J = 1; J <= 4; J++) {
M=M(Winning_Line, J);
letter=X[M];
K1=((M-1)>>4) + 1;
J2=M-((K1-1)*16);
K2=((J2-1)>>2) + 1;
K3=M-((K1-1)*16)-((K2-1)*4);
if (first_player == HUMAN) {
if (letter == HUMAN_PLAY) {
draw_X(K3,K2,K1);
} else if (letter == COMPUTER_PLAY) {
draw_O(K3,K2,K1);
}
} else {
if (letter == COMPUTER_PLAY) {
draw_X(K3,K2,K1);
} else if (letter == HUMAN_PLAY) {
draw_O(K3,K2,K1);
}
}
}
if (lockout_timer == 1) Winning_Line = 0;
}
if (status_ticker > 0U) {
Reset0Ref();
//set_scale(0xdf);
Print_Str_d(0, -100, status);
status_ticker -= 1U;
}
if (!lockout_timer && (!(XJoy > 0))) {
// suppress player's piece when rotating board etc
if (first_player == HUMAN) {
draw_X(player_x, player_y, player_z);
} else { // first_player == MACHINE
draw_O(player_x, player_y, player_z);
}
}
if (tick++ == animation_delay) { // doubling as key debouncing interval
(void)irand127(); // use timing to further randomise
if (!lockout_timer) {
if (YJoy < 0) {
if (highlighted_menu_item > FIRST_MENU_ITEM) highlighted_menu_item -= 1;
} else if (YJoy > 0) {
if (highlighted_menu_item < LAST_MENU_ITEM) highlighted_menu_item += 1;
}
if (step_x || step_y || step_z) {
hint_timer = 0L; HintLoc = -110; // reset timeout if any button pressed...
status[0] = '\x80'; status[1] = '\0'; status_ticker = 0;
}
if (step_x && !previous_step_x) player_x++; if (player_x == 5) player_x = 1;
if (step_y && !previous_step_y) player_y++; if (player_y == 5) player_y = 1;
if (step_z && !previous_step_z) player_z++; if (player_z == 5) player_z = 1;
if (place_mark && (!previous_place_mark) && (!(XJoy > 0))) {
for (I=1; I <= 64; I++) Marked[I]=UNMARKED; // Remove temporary markers which are not used by human
Move=xyz(player_x, player_y, player_z);
if (X[Move] == 0) { // square is free, no X's or O's on it.
X[Move]=HUMAN_PLAY; // Place human's mark in the selected square
whats_next=COMPUTER_MOVE_WANTED;
// TO DO: make a cheerful 'ding' sound to indicate human has placed their marker?
} else {
// INITIAL SOUND TEST - thrrrp if square is already occupied...
play_explosion(&explosion_type_1);
STATUS("OCCUPIED");
}
} // button 1 pressed
previous_step_x = step_x; previous_step_y = step_y; previous_step_z = step_z; previous_place_mark = place_mark;
step_x = step_y = step_z = place_mark = 0;
} // lockout timer
tick = 0;
if (hint_timer >= HINT_DELAY) {
HintLoc++; if (HintLoc == 110) HintLoc = -110;
}
} // animation delay
if (lockout_timer) lockout_timer -= 1U; // every frame
if (hint_timer < 32767L) hint_timer += 1L; // max out to avoid wraparound
}
// actually this code never exits so this is just here as a reminder in case there's some really
// long sound I forgot to cut off, this will remind me to set up a timer and kill it explicitly
Clear_Sound();
return 0;
}