#include <vectrex.h> // position-independent code to draw a circle at x,y extracted from circle.bin using: #ifdef NEVER #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { FILE *circ = fopen("pang8_0.bin", "rb"); int c1, c2, c3, c4; c1 = fgetc(circ); c2 = fgetc(circ); c3 = fgetc(circ); c4 = fgetc(circ); for (;;) { if (c1 == EOF) exit(1); if (c1 == c2 && c3 == c4 && c2 == c3 && c1 == 'x') break; // remainder of file is relocatable code! c1 = c2; c2 = c3; c3 = c4; c4 = fgetc(circ); } fprintf(stdout, "const unsigned char drawcirc[] = {\n "); c1 = 15; c3 = 0; for (;;) { c2 = fgetc(circ); if (c2 == EOF) break; fprintf(stdout, " %3d,", c2); c3++; if ((c3&c1) == 0) fprintf(stdout, "\n "); } fprintf(stdout, "\n};\n"); exit(0); } #endif // the position-independent code in circle.asm follows the string "xxxx" which is used // as a delimeter... const unsigned char drawcirc[] = { 134, 204, 151, 12, 134, 129, 151, 0, 134, 79, 151, 1, 134, 132, 151, 0, 12, 0, 134, 152, 151, 11, 134, 127, 183, 208, 4, 134, 129, 151, 0, 134, 204, 151, 12, 134, 95, 151, 1, 134, 132, 151, 0, 12, 0, 134, 152, 151, 11, 55, 6, 151, 1, 15, 0, 52, 6, 134, 206, 151, 12, 15, 10, 12, 0, 215, 1, 15, 5, 53, 6, 77, 42, 4, 64, 40, 1, 74, 93, 42, 4, 80, 40, 1, 90, 231, 127, 170, 127, 198, 64, 129, 64, 35, 19, 129, 100, 35, 4, 134, 8, 32, 2, 134, 4, 213, 13, 39, 252, 74, 38, 253, 32, 4, 213, 13, 39, 252, 15, 11, 134, 128, 151, 0, 134, 246, 151, 1, 134, 129, 151, 0, 134, 255, 151, 1, 134, 238, 151, 12, 134, 1, 151, 0, 134, 253, 151, 1, 134, 250, 151, 1, 134, 246, 151, 1, 134, 226, 151, 1, 134, 129, 151, 0, 134, 128, 151, 0, 134, 10, 151, 1, 134, 129, 151, 0, 134, 226, 151, 1, 134, 1, 151, 0, 134, 246, 151, 1, 134, 250, 151, 1, 134, 253, 151, 1, 134, 255, 151, 1, 134, 129, 151, 0, 134, 1, 151, 1, 134, 1, 151, 0, 134, 3, 151, 1, 134, 3, 151, 1, 134, 6, 151, 1, 134, 10, 151, 1, 134, 30, 151, 1, 134, 129, 151, 0, 134, 128, 151, 0, 134, 246, 151, 1, 134, 129, 151, 0, 134, 30, 151, 1, 134, 1, 151, 0, 134, 10, 151, 1, 134, 6, 151, 1, 134, 3, 151, 1, 134, 1, 151, 1, 134, 129, 151, 0, 134, 204, 151, 12, 134, 111, 151, 1, 134, 132, 151, 0, 12, 0, 134, 152, 151, 11, 134, 65, 198, 131, 151, 1, 15, 0, 52, 6, 134, 206, 151, 12, 15, 10, 12, 0, 215, 1, 15, 5, 53, 6, 77, 42, 4, 64, 40, 1, 74, 93, 42, 4, 80, 40, 1, 90, 231, 127, 170, 127, 198, 64, 129, 64, 35, 19, 129, 100, 35, 4, 134, 8, 32, 2, 134, 4, 213, 13, 39, 252, 74, 38, 253, 32, 4, 213, 13, 39, 252, 57, }; #define gosub(x,y,func) \ asm(" lda %0\n" \ " ldb %1\n" \ " leau -16,s\n" \ " pshu d\n" \ " jsr _drawcirc\n" : /* outputs */ \ : "g" (x), "g" (y) /* inputs */ \ : "d", "x", "y", "u"); /* clobbered */ void draw_circle(int x, int y) { gosub(x,y,drawcirc); } // sound does not work yet. As well as feedback sound for placing or failing // to place your mark, also need a little tune when either player wins. #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. 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; } // int SY, SX; // z -= 1; x -= 1; y -= 1; // SX = y*16+x*16-127+51; // 3,3,3 -> 3*16 + 3*16 - 127 +51 = 48+48 -127 +51 // SY = y*8+z*64-127; // 3*16 + 3*75 - 127 -16 = 48 + 225 - 127 -16 = 273-127-16 = 130 // x1 = -39+(r1-1)*8+(c1-1)*8; y1 = -72+(l1-1)*38+(r1-1)*8; // x2 = -39+(r2-1)*8+(c2-1)*8; y2 = -72+(l2-1)*38+(r2-1)*8; r1 -= 1; c1 -= 1; l1 -= 1; r2 -= 1; c2 -= 1; l2 -= 1; x1 = r1*16+c1*16-127+51; // 3,3,3 -> 3*16 + 3*16 - 127 +51 = 48+48 -127 +51 y1 = r1*8+l1*64-127; // 3*16 + 3*75 - 127 -16 = 48 + 225 - 127 -16 = 273-127-16 = 130 x2 = r2*16+c2*16-127+51; // 3,3,3 -> 3*16 + 3*16 - 127 +51 = 48+48 -127 +51 y2 = r2*8+l2*64-127; // 3*16 + 3*75 - 127 -16 = 48 + 225 - 127 -16 = 273-127-16 = 130 Intensity_7F(); Reset0Ref(); set_scale(0x80); Moveto_d(y1,x1); Lineto_d(y2-y1,x2-x1); } // 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_O(int x, int y, int z) { #ifdef DIAMOND_APPROXIMATION 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); #else int SY, SX; Reset0Ref(); z -= 1; x -= 1; y -= 1; SX = y*16+x*16-127+51; // 3,3,3 -> 3*16 + 3*16 - 127 +51 = 48+48 -127 +51 SY = y*8+z*64-(z>>1)-127; // 3*16 + 3*75 - 127 -16 = 48 + 225 - 127 -16 = 273-127-16 = 130 draw_circle(SY,SX); #endif } static inline void draw_X(int x, int y, int z) { #ifdef DIAMOND_APPROXIMATION 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); #else int SY, SX; // 0 1 2 3 // 0 0 1 2 // 0 0 2 4 z -= 1; x -= 1; y -= 1; SX = y*16+x*16-127+51; // 3,3,3 -> 3*16 + 3*16 - 127 +51 = 48+48 -127 +51 SY = y*8+z*64-(z>>1)-127; // 3*16 + 3*75 - 127 -16 = 48 + 225 - 127 -16 = 273-127-16 = 130 Reset0Ref(); set_scale(0x80); Moveto_d(SY,SX); set_scale(0x10); MoveRel(-70,-16); LineRel(127, 40); MoveRel(-54,-40); LineRel(-4, 40); #endif } void DrawGrid(int z) { z -= 3; Reset0Ref(); set_scale(0xd2); Moveto_d(z*39-2, -57); set_scale(0x40); Lineto_d(62, 124); Moveto_d(0, 31); Lineto_d(-62, -124); Moveto_d(0, 31); Lineto_d(62, 124); Moveto_d(0, 31); Lineto_d(-62, -124); Moveto_d(0, 31); Lineto_d(62, 124); Reset0Ref(); set_scale(0xd2); Moveto_d(z*39-2, -57); set_scale(0x40); Lineto_d(0, 124); Moveto_d(16,31); Lineto_d(0, -124); Moveto_d(16,31); Lineto_d(0, 124); Moveto_d(16,31); Lineto_d(0, -124); Moveto_d(16,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)<<4); K2=((J2-1)>>2) + 1; // Y K3=M-((K1-1)<<4)-((K2-1)<<2); // 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; } // static int Yes_or_No; // state machine: #define COMPUTER_MOVE_WANTED 1 #define HUMAN_MOVE_WANTED 2 #define PLAY_AGAIN_WANTED 3 // user input: // these will be on the RHS and selectable using the joystick... not yet implemented #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, 40, "FLIP\x80"); if (selected == FLIP_REQUESTED) Intensity_5F(); if (selected == SCRAMBLE_REQUESTED) Intensity_7F(); Print_Str_d(-40, 40, "SCRAMBLE\x80"); if (selected == SCRAMBLE_REQUESTED) Intensity_5F(); if (selected == INVERT_REQUESTED) Intensity_7F(); Print_Str_d(-20, 40, "INVERT\x80"); if (selected == INVERT_REQUESTED) Intensity_5F(); if (selected == ROTATE_REQUESTED) Intensity_7F(); Print_Str_d(0, 40, "ROTATE\x80"); if (selected == ROTATE_REQUESTED) Intensity_5F(); if (selected == TUMBLE_REQUESTED) Intensity_7F(); Print_Str_d(20, 40, "TUMBLE\x80"); if (selected == TUMBLE_REQUESTED) Intensity_5F(); if (selected == WHIRL_REQUESTED) Intensity_7F(); Print_Str_d(40, 40, "WHIRL\x80"); if (selected == WHIRL_REQUESTED) Intensity_5F(); //if (selected == HELP_REQUESTED) Intensity_7F(); //Print_Str_d(60, 40, "HELP\x80"); //if (selected == HELP_REQUESTED) Intensity_5F(); if (selected == QUIT_REQUESTED) Intensity_7F(); Print_Str_d(80, 40, "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? //for (I=1; I <= 64; I++) X[I]=0; //first_player = 1-first_player; //if (first_player == MACHINE) return COMPUTER_MOVE_WANTED; 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; } } // goto HUMAN_MOVE; } 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; } } // goto HUMAN_MOVE; } } //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!"); //first_player = 1-first_player; 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); } 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 = 127; //-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"); //} 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... Wait_Recal(); // sets dp to d0, and pos at 0, 0 No drawing above this line. if (sound_wanted) { // trying to replicate http://vectorgaming.proboards.com/thread/1850/explosion-snd-example-never-before // but not working too well!!! asm(" pshs d,x,u"); // D-reg, X-reg, U-reg trashed asm(" Jsr 0HF289 ; Do_Sound"); // Do Sound set up below asm(" puls u,x,d"); sound_wanted = 0; } 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) or idx++ for efficiency, once I'm sure this is correct 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) { // could fade this in if the human is taking an inordinate amount of time to press // any buttons, i.e. they're not sure what to do... 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)<<4); K2=((J2-1)>>2) + 1; // Y K3=M-((K1-1)<<4)-((K2-1)<<2); // X M=M(Winning_Line, 4); letter=X[M]; Q1=((M-1)>>4) + 1; // Z J2=M-((Q1-1)<<4); Q2=((J2-1)>>2) + 1; // Y Q3=M-((Q1-1)<<4)-((Q2-1)<<2); // 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)<<4); K2=((J2-1)>>2) + 1; K3=M-((K1-1)<<4)-((K2-1)<<2); 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(-15, -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 = 127; /*-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 'M' is stored packed, 2 bits per coord. // (Range 1..64 (not 0..63 due to BASIC arrays based at 1)) 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 { // Square is occupied. Trying to play a sound to let the player know... // trying to replicate http://vectorgaming.proboards.com/thread/1850/explosion-snd-example-never-before // but not working too well!!! asm(" pshs d,x,y,u,dp"); asm(" bra skipxxx"); asm("EXP1: .byte 25"); asm(" .byte 63"); asm(" .byte 0"); asm(" .byte 2"); asm("EXP2: .byte 63"); asm(" .byte 0"); asm(" .byte 0"); asm(" .byte 1"); asm("EXP3: .byte 63"); asm(" .byte 1"); asm(" .byte 255"); asm(" .byte 2"); asm("skipxxx: Lda #255 ; negative bit set"); asm(" Sta 0HC867 ; Vec_Expl_Flag"); asm(" Jsr 0HF1AF ; DP_to_C8"); asm(" Ldu #EXP3"); asm(" Jsr 0HF92E ; Explosion_Snd"); asm(" puls dp,u,y,x,d"); sound_wanted = 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) { // no longer scrolling this... //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 } return 0; }