820 lines
22 KiB
C
820 lines
22 KiB
C
#include <c64/joystick.h>
|
|
#include <c64/vic.h>
|
|
#include <c64/sprites.h>
|
|
#include <c64/memmap.h>
|
|
#include <c64/rasterirq.h>
|
|
#include <c64/sid.h>
|
|
#include <c64/charwin.h>
|
|
#include <c64/types.h>
|
|
#include <conio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
// Include charset and sprite resources
|
|
|
|
char charset[2048] = {
|
|
#embed "../resources/connect4chars.bin"
|
|
};
|
|
|
|
char spriteset[2048] = {
|
|
#embed "../resources/connect4sprites.bin"
|
|
};
|
|
|
|
// Address ov video data
|
|
|
|
byte * const Screen = (byte *)0xc800;
|
|
byte * const Font = (byte *)0xd000;
|
|
byte * const Color = (byte *)0xd800;
|
|
byte * const Sprites = (byte *)0xd800;
|
|
|
|
// Values for player 1 pieces and player 2 pieces.
|
|
// The values 1 and 5 are selected so that the sum of the
|
|
// values in a row of four will add up to a unique value
|
|
// in the range of 0..24 for ranking
|
|
|
|
#define PLAYER1_INC 1
|
|
#define PLAYER2_INC 5
|
|
|
|
// Current state of the board
|
|
|
|
struct Board
|
|
{
|
|
char fcols[48]; // Pieces in the 6x7 grid using 48 to have a multiplier of 8
|
|
char ffree[7]; // Number of free places in each column
|
|
} board;
|
|
|
|
enum GameState
|
|
{
|
|
GS_READY, // Getting ready
|
|
|
|
GS_PLAYER_MOVE, // The players move
|
|
GS_COMPUTER_MOVE, // The computers move
|
|
|
|
GS_PLAYER_WIN, // The player wins
|
|
GS_COMPUTER_WIN, // The computer wins
|
|
GS_GAME_OVER // No more moves
|
|
};
|
|
|
|
// Draw the board into the CharWin using the small
|
|
// character set
|
|
|
|
void board_draw(CharWin * cwin)
|
|
{
|
|
for(char y=0; y<6; y++)
|
|
{
|
|
for(char x=0; x<7; x++)
|
|
{
|
|
if (board.fcols[8 * y + x] == PLAYER1_INC)
|
|
cwin_putat_char_raw(cwin, x, y, 82, 10);
|
|
else if (board.fcols[8 * y + x] == PLAYER2_INC)
|
|
cwin_putat_char_raw(cwin, x, y, 82, 15);
|
|
else
|
|
cwin_putat_char_raw(cwin, x, y, 82, 8);
|
|
}
|
|
|
|
// Right most column
|
|
|
|
cwin_putat_char_raw(cwin, 7, y, 83, 8);
|
|
}
|
|
}
|
|
|
|
// Draw a big piece or place with the given color, the
|
|
// color 0 denotes an empty place. Empty places use different
|
|
// tiles to allow sprite priority to obscure the falling pieces
|
|
|
|
void board_draw_item(CharWin * cwin, char x, char y, char c)
|
|
{
|
|
char cx = x * 3 + 1, cy = y * 3 + 2;
|
|
|
|
if (c)
|
|
{
|
|
cwin_putat_char_raw(cwin, cx + 0, cy + 0, 73, c);
|
|
cwin_putat_char_raw(cwin, cx + 1, cy + 0, 74, c);
|
|
cwin_putat_char_raw(cwin, cx + 2, cy + 0, 75, c);
|
|
|
|
cwin_putat_char_raw(cwin, cx + 0, cy + 1, 76, c);
|
|
cwin_putat_char_raw(cwin, cx + 1, cy + 1, 77, c);
|
|
cwin_putat_char_raw(cwin, cx + 2, cy + 1, 78, c);
|
|
|
|
cwin_putat_char_raw(cwin, cx + 0, cy + 2, 79, c);
|
|
cwin_putat_char_raw(cwin, cx + 1, cy + 2, 80, c);
|
|
cwin_putat_char_raw(cwin, cx + 2, cy + 2, 81, c);
|
|
}
|
|
else
|
|
{
|
|
cwin_putat_char_raw(cwin, cx + 0, cy + 0, 64, 14);
|
|
cwin_putat_char_raw(cwin, cx + 1, cy + 0, 65, 14);
|
|
cwin_putat_char_raw(cwin, cx + 2, cy + 0, 66, 14);
|
|
|
|
cwin_putat_char_raw(cwin, cx + 0, cy + 1, 67, 14);
|
|
cwin_putat_char_raw(cwin, cx + 1, cy + 1, 68, 14);
|
|
cwin_putat_char_raw(cwin, cx + 2, cy + 1, 69, 14);
|
|
|
|
cwin_putat_char_raw(cwin, cx + 0, cy + 2, 70, 14);
|
|
cwin_putat_char_raw(cwin, cx + 1, cy + 2, 71, 14);
|
|
cwin_putat_char_raw(cwin, cx + 2, cy + 2, 72, 14);
|
|
}
|
|
}
|
|
|
|
// Draw the big board
|
|
|
|
void board_draw_main(CharWin * cwin)
|
|
{
|
|
for(char y=0; y<6; y++)
|
|
{
|
|
for(char x=0; x<7; x++)
|
|
{
|
|
if (board.fcols[8 * y + x] == PLAYER1_INC)
|
|
board_draw_item(cwin, x, y, 10);
|
|
else if (board.fcols[8 * y + x] == PLAYER2_INC)
|
|
board_draw_item(cwin, x, y, 15);
|
|
else
|
|
board_draw_item(cwin, x, y, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw the decoration of the big board
|
|
|
|
void board_init_main(CharWin * cwin)
|
|
{
|
|
// Frame around the board
|
|
|
|
cwin_putat_char_raw(cwin, 0, 1, 84, 14);
|
|
cwin_fill_rect_raw(cwin, 1, 1, 21, 1, 85, 14);
|
|
cwin_putat_char_raw(cwin, 22, 1, 86, 14);
|
|
cwin_fill_rect_raw(cwin, 0, 2, 1, 18, 87, 14);
|
|
cwin_fill_rect_raw(cwin, 22, 2, 1, 18, 88, 14);
|
|
cwin_putat_char_raw(cwin, 0, 20, 89, 14);
|
|
cwin_fill_rect_raw(cwin, 1, 20, 21, 1, 90, 14);
|
|
cwin_putat_char_raw(cwin, 22, 20, 91, 14);
|
|
|
|
// Fields in the board
|
|
|
|
for(char x=0; x<7; x++)
|
|
cwin_putat_char_raw(cwin, 3 * x + 2, 0, '1' + x, 1);
|
|
|
|
board_draw_main(cwin);
|
|
}
|
|
|
|
// Play animation of a dropped piece
|
|
|
|
void item_drop_anim(CharWin * cwin, char x, char y, char c)
|
|
{
|
|
// Initial position
|
|
|
|
int ix = (cwin->sx + 3 * x + 1) * 8 + 24, iy = (cwin->sy + 2) * 8 + 20;
|
|
|
|
// Show sprite at start position
|
|
spr_set(0, true, ix, iy, 97, c, true, false, false);
|
|
|
|
// Set sprite priority to be behind background
|
|
vic.spr_priority = 0x01;
|
|
|
|
// Speed and target position, using four fractional bits
|
|
int vy = 0, aiy = iy * 16, ty = ((cwin->sy + y * 3 + 2) * 8 + 50) * 16;
|
|
|
|
// Bounce three times back when reaching bottom
|
|
char bounce = 3;
|
|
|
|
// Loop through the bounces
|
|
while (bounce > 0)
|
|
{
|
|
// Let gravity do its thing of accelerating
|
|
vy += 3;
|
|
|
|
// Add vertical velocity to position
|
|
aiy += vy;
|
|
|
|
// Reached the bottom yet?
|
|
if (aiy > ty)
|
|
{
|
|
// Reflect position
|
|
aiy = 2 * ty - aiy;
|
|
|
|
// Reflect speed
|
|
vy = - vy * 5 >> 4;
|
|
|
|
// One bounce down
|
|
bounce--;
|
|
}
|
|
|
|
// Move sprite and wait for display
|
|
spr_move(0, ix, aiy >> 4);
|
|
vic_waitFrame();
|
|
}
|
|
}
|
|
|
|
// Play column selection animation
|
|
|
|
void column_select_anim(CharWin * cwin, char x0, char x1, char c)
|
|
{
|
|
// Start position
|
|
int ix = (cwin->sx + 3 * x0 + 1) * 8 + 24, iy = (cwin->sy + 2) * 8 + 20;
|
|
|
|
// Target position
|
|
int tx = (cwin->sx + 3 * x1 + 1) * 8 + 24;
|
|
|
|
// Show sprite at start position
|
|
spr_set(0, true, ix, iy, 97, c, true, false, false);
|
|
|
|
// Set sprite priority to be behind background
|
|
vic.spr_priority = 0x01;
|
|
|
|
// Not at target position already?
|
|
if (ix != tx)
|
|
{
|
|
// Eight frames for movement
|
|
for(int i=1; i<=8; i++)
|
|
{
|
|
vic_waitFrame();
|
|
// Move sprite along path
|
|
spr_move(0, (ix * (8 - i) + tx * i) >> 3, iy);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize the board to empty
|
|
|
|
void board_init(void)
|
|
{
|
|
// Column has six free slots
|
|
for(char x=0; x<7; x++)
|
|
board.ffree[x] = 6;
|
|
|
|
// Clear all slots
|
|
for(char i=0; i<48; i++)
|
|
board.fcols[i] = 0;
|
|
}
|
|
|
|
// Drop one piece of player p down column x
|
|
|
|
inline bool board_drop(char x, bool p)
|
|
{
|
|
// Check for free space
|
|
if (board.ffree[x])
|
|
{
|
|
// Fill one slot
|
|
char y = --(board.ffree[x]);
|
|
board.fcols[8 * y + x] = p ? PLAYER2_INC : PLAYER1_INC;
|
|
|
|
// Success
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Remove the top piece from column x
|
|
|
|
inline void board_undrop(char x)
|
|
{
|
|
// Free one slot
|
|
char y = (board.ffree[x])++;
|
|
board.fcols[8 * y + x] = 0x00;
|
|
}
|
|
|
|
// Score for rows with 0, 1, 2, and 3 elements of same color
|
|
|
|
static const int score[5] = {0, 1, 8, 128, 10000};
|
|
|
|
// Score index is calculated (25 * rv + 5 * p1 + p2) with rv the
|
|
// value range of the row, p1 and p2 number of pieces from player 1 or 2
|
|
|
|
int fscore[125];
|
|
|
|
// Indices of the board positions of four slots of each of the 69
|
|
// 4 slot rows in the board. The fifth value is the value range
|
|
// of the row
|
|
|
|
char frows[5][69];
|
|
|
|
#pragma align(fscore, 256)
|
|
|
|
// Tables for opening library for computer move 1, 2, 3 and 4
|
|
|
|
const char open1 = 3;
|
|
const char open2[4 * 7] =
|
|
{
|
|
4, 3, 3, 3, 4, 3, 3, 3, 5, 3, 3, 4, 3, 3, 3, 3, 2, 3, 2, 2, 2, 4, 2, 4, 3, 2, 4, 2
|
|
};
|
|
|
|
const char open3[4 * 7 * 7] = {
|
|
0, 5, 2, 2, 5, 3, 2, 5, 4, 2, 4, 3, 3, 5, 2, 2, 3, 2, 3, 2, 2, 4, 5, 2, 4, 4, 3, 2,
|
|
3, 3, 3, 3, 3, 3, 4, 1, 3, 2, 5, 3, 2, 1, 2, 5, 2, 2, 4, 1, 4, 5, 4, 2, 4, 3, 3, 5,
|
|
4, 1, 3, 4, 4, 4, 3, 2, 1, 3, 2, 3, 2, 3, 5, 5, 1, 5, 4, 3, 3, 3, 4, 3, 3, 5, 3, 3,
|
|
3, 5, 2, 3, 4, 1, 3, 5, 4, 3, 1, 4, 3, 5, 2, 2, 3, 2, 3, 2, 2, 2, 1, 3, 2, 3, 2, 3,
|
|
3, 1, 3, 2, 2, 3, 3, 2, 1, 3, 2, 2, 2, 2, 3, 3, 3, 2, 1, 3, 3, 3, 3, 1, 3, 3, 2, 3,
|
|
2, 3, 3, 3, 3, 3, 3, 3, 2, 1, 2, 3, 4, 3, 2, 4, 3, 2, 2, 2, 2, 1, 0, 2, 4, 3, 4, 4,
|
|
4, 4, 1, 2, 5, 2, 2, 2, 2, 3, 2, 4, 6, 5, 4, 4, 4, 4, 3, 2, 4, 3, 2, 3, 4, 5, 4, 3,
|
|
};
|
|
|
|
const char open4[4 * 7 * 7 * 7] = {
|
|
2, 5, 2, 2, 5, 3, 2, 2, 2, 6, 2, 2, 2, 2, 0, 4, 4, 3, 2, 3, 3, 5, 5, 5, 5, 5, 1, 1,
|
|
2, 2, 6, 2, 2, 2, 2, 0, 4, 4, 4, 3, 5, 4, 1, 5, 1, 1, 1, 1, 1, 0, 4, 2, 4, 3, 4, 5,
|
|
5, 1, 4, 5, 5, 3, 2, 3, 3, 1, 1, 1, 1, 3, 5, 5, 4, 5, 5, 4, 4, 3, 3, 3, 1, 3, 3, 3,
|
|
3, 3, 3, 1, 3, 3, 3, 5, 4, 2, 5, 3, 1, 4, 0, 3, 6, 2, 3, 3, 3, 3, 3, 1, 1, 1, 1, 3,
|
|
3, 3, 3, 2, 3, 3, 3, 2, 1, 6, 2, 4, 5, 2, 3, 3, 3, 2, 3, 3, 3, 3, 1, 5, 5, 3, 3, 3,
|
|
3, 3, 0, 2, 3, 3, 3, 0, 5, 2, 2, 5, 1, 2, 4, 4, 2, 4, 5, 4, 2, 2, 2, 3, 2, 2, 2, 2,
|
|
2, 5, 5, 2, 2, 1, 2, 4, 3, 4, 4, 3, 3, 4, 1, 3, 2, 1, 4, 4, 1, 4, 5, 4, 4, 4, 2, 4,
|
|
0, 3, 3, 4, 3, 3, 3, 3, 3, 3, 1, 3, 5, 3, 3, 3, 3, 2, 2, 4, 3, 4, 3, 4, 4, 3, 3, 4,
|
|
3, 3, 2, 3, 3, 5, 6, 3, 5, 4, 5, 5, 3, 3, 3, 3, 2, 3, 3, 3, 5, 0, 5, 3, 4, 4, 3, 2,
|
|
3, 3, 3, 1, 3, 3, 3, 3, 1, 5, 5, 3, 3, 3, 3, 3, 2, 1, 4, 3, 1, 3, 3, 3, 4, 3, 3, 3,
|
|
2, 2, 3, 2, 5, 5, 4, 2, 5, 3, 1, 4, 2, 1, 0, 2, 4, 4, 3, 2, 4, 5, 4, 2, 5, 3, 1, 4,
|
|
3, 3, 0, 2, 3, 3, 3, 4, 2, 4, 4, 2, 2, 4, 3, 3, 3, 4, 6, 3, 3, 2, 5, 3, 1, 4, 2, 1,
|
|
2, 2, 3, 2, 2, 4, 6, 0, 4, 2, 4, 3, 4, 5, 5, 1, 4, 5, 5, 3, 2, 3, 3, 1, 1, 1, 1, 3,
|
|
5, 5, 4, 5, 5, 4, 4, 3, 3, 3, 1, 3, 3, 3, 3, 3, 3, 1, 3, 3, 3, 5, 4, 2, 5, 3, 1, 4,
|
|
2, 2, 6, 2, 2, 2, 2, 4, 4, 3, 4, 4, 4, 3, 4, 1, 3, 5, 3, 3, 3, 2, 2, 6, 2, 2, 2, 2,
|
|
5, 1, 3, 3, 5, 4, 3, 2, 2, 6, 2, 2, 2, 2, 4, 1, 3, 5, 3, 1, 3, 3, 3, 1, 1, 1, 1, 3,
|
|
6, 2, 3, 1, 4, 5, 4, 3, 3, 3, 2, 3, 3, 3, 1, 2, 3, 1, 4, 1, 1, 3, 3, 3, 4, 3, 2, 3,
|
|
1, 3, 1, 1, 4, 2, 1, 3, 3, 3, 2, 3, 3, 3, 4, 4, 2, 4, 5, 4, 2, 4, 1, 6, 4, 4, 4, 2,
|
|
1, 3, 2, 4, 3, 1, 1, 4, 4, 4, 4, 4, 4, 2, 3, 3, 4, 4, 3, 3, 4, 3, 5, 3, 1, 3, 1, 3,
|
|
5, 2, 2, 5, 4, 3, 5, 3, 3, 3, 1, 3, 5, 3, 3, 1, 4, 4, 5, 3, 3, 3, 1, 3, 2, 2, 2, 3,
|
|
3, 3, 4, 4, 3, 3, 4, 3, 5, 3, 3, 5, 5, 3, 5, 4, 2, 5, 5, 3, 5, 3, 3, 3, 1, 1, 5, 3,
|
|
3, 3, 3, 1, 3, 3, 3, 5, 1, 1, 3, 4, 3, 3, 1, 3, 1, 1, 4, 2, 1, 5, 5, 2, 1, 4, 1, 1,
|
|
5, 4, 2, 5, 5, 3, 5, 3, 3, 2, 3, 5, 5, 1, 3, 3, 3, 5, 3, 3, 3, 5, 4, 2, 5, 3, 1, 4,
|
|
2, 1, 1, 4, 3, 4, 4, 3, 3, 3, 2, 3, 3, 3, 5, 3, 2, 5, 4, 3, 3, 3, 3, 3, 1, 1, 5, 3,
|
|
3, 3, 3, 5, 3, 3, 3, 4, 3, 2, 2, 3, 1, 6, 0, 3, 6, 2, 3, 3, 3, 3, 3, 1, 1, 1, 1, 3,
|
|
3, 3, 3, 2, 3, 3, 3, 2, 1, 6, 2, 4, 5, 2, 3, 3, 3, 2, 3, 3, 3, 3, 1, 5, 5, 3, 3, 3,
|
|
3, 3, 0, 2, 3, 3, 3, 3, 3, 1, 1, 1, 1, 3, 6, 2, 3, 1, 4, 5, 4, 3, 3, 3, 2, 3, 3, 3,
|
|
1, 2, 3, 1, 4, 1, 1, 3, 3, 3, 4, 3, 2, 3, 1, 3, 1, 1, 4, 2, 1, 3, 3, 3, 2, 3, 3, 3,
|
|
3, 3, 3, 4, 3, 3, 3, 2, 3, 1, 3, 0, 1, 1, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 2, 2, 3,
|
|
4, 4, 4, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 3, 2, 2, 2, 2,
|
|
1, 3, 2, 4, 3, 1, 1, 0, 1, 2, 2, 4, 5, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
|
2, 3, 3, 2, 2, 3, 3, 2, 3, 3, 2, 2, 3, 2, 3, 1, 3, 4, 3, 3, 3, 1, 1, 4, 4, 2, 4, 2,
|
|
3, 4, 3, 4, 3, 4, 4, 3, 2, 3, 2, 3, 3, 3, 1, 2, 2, 2, 4, 4, 2, 3, 4, 4, 4, 3, 5, 3,
|
|
3, 2, 4, 4, 3, 3, 3, 3, 1, 5, 5, 3, 3, 3, 1, 3, 1, 1, 4, 2, 1, 3, 1, 1, 3, 3, 1, 3,
|
|
2, 3, 3, 2, 2, 3, 3, 3, 4, 4, 4, 3, 5, 3, 3, 3, 1, 2, 2, 5, 3, 3, 1, 3, 5, 3, 3, 3,
|
|
1, 3, 3, 3, 4, 3, 3, 3, 3, 1, 1, 2, 1, 3, 0, 1, 3, 3, 4, 3, 3, 2, 3, 3, 2, 2, 3, 2,
|
|
3, 2, 4, 4, 3, 3, 3, 3, 1, 3, 5, 3, 3, 3, 3, 3, 3, 2, 3, 3, 6, 0, 2, 1, 2, 3, 4, 4,
|
|
4, 1, 3, 3, 4, 4, 2, 4, 3, 2, 4, 3, 3, 4, 3, 3, 2, 4, 4, 4, 3, 3, 2, 1, 4, 3, 3, 3,
|
|
4, 2, 1, 4, 5, 4, 2, 4, 2, 1, 2, 3, 4, 4, 2, 2, 1, 2, 1, 2, 2, 1, 1, 3, 3, 3, 5, 1,
|
|
2, 5, 3, 2, 5, 3, 3, 2, 3, 1, 1, 1, 6, 2, 1, 4, 4, 1, 4, 6, 5, 2, 2, 1, 6, 6, 2, 4,
|
|
2, 2, 1, 2, 5, 4, 2, 4, 3, 2, 4, 3, 3, 4, 4, 3, 3, 3, 3, 1, 1, 1, 0, 3, 3, 3, 3, 3,
|
|
1, 0, 2, 2, 4, 5, 2, 1, 0, 3, 1, 3, 1, 3, 1, 0, 2, 5, 2, 2, 5, 1, 0, 3, 2, 2, 5, 2,
|
|
2, 2, 1, 4, 4, 4, 2, 2, 4, 0, 2, 4, 2, 4, 5, 4, 2, 1, 5, 4, 1, 2, 1, 2, 2, 2, 4, 2,
|
|
5, 2, 1, 5, 4, 2, 1, 2, 4, 2, 4, 6, 2, 4, 4, 2, 2, 2, 5, 4, 4, 4, 1, 4, 2, 3, 6, 5,
|
|
1, 4, 4, 1, 4, 6, 5, 3, 5, 3, 5, 3, 6, 5, 2, 1, 2, 4, 4, 6, 5, 3, 3, 3, 3, 3, 6, 5,
|
|
5, 5, 3, 3, 3, 3, 2, 2, 3, 3, 2, 4, 3, 2, 4, 2, 1, 4, 5, 4, 2, 2, 4, 0, 0, 5, 4, 2,
|
|
1, 0, 2, 5, 2, 2, 5, 4, 0, 5, 5, 5, 3, 4, 3, 3, 1, 4, 3, 1, 2, 5, 1, 3, 3, 3, 5, 5,
|
|
2, 2, 5, 4, 5, 4, 4, 2, 2, 3, 4, 5, 4, 2, 2, 2, 1, 2, 5, 4, 2, 3, 3, 3, 2, 5, 4, 3,
|
|
3, 2, 2, 2, 4, 3, 3, 2, 3, 3, 2, 4, 3, 2, 4, 2, 2, 3, 3, 5, 2, 2, 2, 3, 4, 5, 4, 6,
|
|
};
|
|
|
|
// Init the ai tables
|
|
void ai_init(void)
|
|
{
|
|
// Loop over 5 value ranges, and 0 to 4 pieces of each player
|
|
// in a row
|
|
|
|
for(char k=0; k<5; k++)
|
|
{
|
|
for(char i=0; i<5; i++)
|
|
{
|
|
for(char j=0; j<5; j++)
|
|
{
|
|
if (i && j)
|
|
fscore[25 * k + PLAYER2_INC * i + j] = 0;
|
|
else if (i == 4 || j == 4)
|
|
fscore[25 * k + PLAYER2_INC * i + j] = score[i] - score[j];
|
|
else
|
|
fscore[25 * k + PLAYER2_INC * i + j] = (score[i] - score[j]) * (1 + k);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create the indices of all 69 rows
|
|
|
|
char p = 0;
|
|
|
|
// Vertical rows
|
|
for(char x=0; x<7; x++)
|
|
{
|
|
for(char y=0; y<3; y++)
|
|
{
|
|
frows[0][p] = x + 8 * y;
|
|
frows[1][p] = x + 8 * y + 8;
|
|
frows[2][p] = x + 8 * y + 16;
|
|
frows[3][p] = x + 8 * y + 24;
|
|
frows[4][p] = 25 * (y + 1);
|
|
p++;
|
|
}
|
|
}
|
|
|
|
for(char x=0; x<4; x++)
|
|
{
|
|
// Horizontal rows
|
|
for(char y=0; y<6; y++)
|
|
{
|
|
frows[0][p] = x + 8 * y;
|
|
frows[1][p] = x + 8 * y + 1;
|
|
frows[2][p] = x + 8 * y + 2;
|
|
frows[3][p] = x + 8 * y + 3;
|
|
frows[4][p] = y > 0 ? 25 * (y - 1) : 0;
|
|
p++;
|
|
}
|
|
|
|
// Diagonal down rows
|
|
for(char y=0; y<3; y++)
|
|
{
|
|
frows[0][p] = x + 8 * y;
|
|
frows[1][p] = x + 8 * y + 9;
|
|
frows[2][p] = x + 8 * y + 18;
|
|
frows[3][p] = x + 8 * y + 27;
|
|
frows[4][p] = 25 * (y + 1);
|
|
p++;
|
|
}
|
|
|
|
// Diagonal up rows
|
|
for(char y=3; y<6; y++)
|
|
{
|
|
frows[0][p] = x + 8 * y;
|
|
frows[1][p] = x + 8 * y - 7;
|
|
frows[2][p] = x + 8 * y - 14;
|
|
frows[3][p] = x + 8 * y - 21;
|
|
frows[4][p] = 25 * (y - 2);
|
|
p++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Evaluate the current board state for the player
|
|
|
|
int board_eval(bool player)
|
|
{
|
|
int score = 0;
|
|
|
|
// Loop through all rows
|
|
for(char r=0; r<69; r++)
|
|
{
|
|
// Build score index for row
|
|
char sum =
|
|
board.fcols[frows[0][r]] +
|
|
board.fcols[frows[1][r]] +
|
|
board.fcols[frows[2][r]] +
|
|
board.fcols[frows[3][r]] +
|
|
frows[4][r];
|
|
|
|
// Help compiler to avoid two byte pointer arithmetic
|
|
__assume(sum < 128);
|
|
|
|
// Add score to board score
|
|
score += fscore[sum];
|
|
}
|
|
|
|
// Check if there was a row with four winning pieces and
|
|
// normalize to +/- 32000 then
|
|
|
|
if (score < -8192)
|
|
score = -32000;
|
|
else if (score >= 8192)
|
|
score = 32000;
|
|
|
|
// Invert score for other player
|
|
|
|
return player ? score : -score;
|
|
}
|
|
|
|
CharWin cwt, cws;
|
|
CharWin cw, cwm;
|
|
|
|
char buff[20];
|
|
|
|
static const char optx[7] = {3, 2, 4, 1, 5, 0, 6};
|
|
|
|
// Find a good move for the player using alpha/beta pruning
|
|
// see: https://en.wikipedia.org/wiki/Alpha-beta_pruning
|
|
|
|
int board_check(char level, int alpha, int beta, bool player)
|
|
{
|
|
int best = alpha;
|
|
bool checked = false;
|
|
|
|
// Current score to cutof early if clear win condition
|
|
int val = board_eval(player) + level;
|
|
|
|
// Best move
|
|
char zx = 0;
|
|
|
|
// Check for end of search
|
|
if (level < 5 && val < 5000 && val > -5000)
|
|
{
|
|
// Update current check display at level 3
|
|
if (level == 3)
|
|
{
|
|
board_draw(&cwt);
|
|
itoa(val, buff, 10);
|
|
cwin_fill_rect(&cwt, 0, 6, 7, 1, ' ', 1);
|
|
cwin_putat_string(&cwt, 0, 6, buff, 1);
|
|
}
|
|
|
|
// Check all seven columns
|
|
for(char i=0; i<7; i++)
|
|
{
|
|
// Start check in the center, due to higher value of
|
|
// pieces there
|
|
|
|
char ix = optx[i];
|
|
|
|
// Try to drop a piece
|
|
|
|
if (board_drop(ix, player))
|
|
{
|
|
// We still have a move
|
|
checked = true;
|
|
|
|
// Check score of position
|
|
int score = - board_check(level + 1, -beta, -best, !player);
|
|
|
|
// Better than what we have so far
|
|
if (score > best)
|
|
{
|
|
// Remember
|
|
best = score;
|
|
zx = ix;
|
|
|
|
// Update best move if on level zero
|
|
if (level == 0)
|
|
{
|
|
board_draw(&cw);
|
|
itoa(best, buff, 10);
|
|
cwin_fill_rect(&cw, 0, 6, 7, 1, ' ', 1);
|
|
cwin_putat_string(&cw, 0, 6, buff, 1);
|
|
}
|
|
|
|
// Check for beta pruning
|
|
if (best > beta)
|
|
{
|
|
board_undrop(ix);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Undo move
|
|
board_undrop(ix);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return new position if first move level
|
|
if (level == 0)
|
|
return zx;
|
|
else if (checked)
|
|
return best;
|
|
else
|
|
return val;
|
|
}
|
|
|
|
// Return a move from the opening library if available
|
|
|
|
char board_opening(char step, char * moves)
|
|
{
|
|
switch (step)
|
|
{
|
|
case 1:
|
|
return open1;
|
|
case 2:
|
|
if (moves[0] < 4)
|
|
return open2[moves[0] * 7 + moves[1]];
|
|
else
|
|
return 6 - open2[(6 - moves[0]) * 7 + (6 - moves[1])];
|
|
|
|
case 3:
|
|
if (moves[0] < 4)
|
|
return open3[moves[0] * 49 + moves[1] * 7 + moves[2]];
|
|
else
|
|
return 6 - open3[(6 - moves[0]) * 49 + (6 - moves[1]) * 7 + (6 - moves[2])];
|
|
|
|
case 4:
|
|
if (moves[0] < 4)
|
|
return open4[moves[0] * 343 + moves[1] * 49 + moves[2] * 7 + moves[3]];
|
|
else
|
|
return 6 - open4[(6 - moves[0]) * 343 + (6 - moves[1]) * 49 + (6 - moves[2]) * 7 + (6 - moves[3])];
|
|
|
|
default:
|
|
return 0xff;
|
|
}
|
|
}
|
|
|
|
// Current state of the game
|
|
struct Game
|
|
{
|
|
GameState state; // State
|
|
char count; // Auto continue counter
|
|
char posx; // Column osition of new piece
|
|
char step; // Number of player moves
|
|
char moves[21]; // Pieces placed by player
|
|
|
|
} TheGame;
|
|
|
|
// Set new game state
|
|
|
|
void game_state(GameState state)
|
|
{
|
|
// Set new state
|
|
TheGame.state = state;
|
|
|
|
// Clear status line
|
|
|
|
cwin_fill_rect(&cws, 0, 0, 40, 1, ' ', 1);
|
|
|
|
switch (state)
|
|
{
|
|
case GS_READY:
|
|
// Start play in one second
|
|
TheGame.count = 60;
|
|
TheGame.step = 0;
|
|
|
|
// Clear the board
|
|
board_init();
|
|
board_init_main(&cwm);
|
|
break;
|
|
|
|
case GS_PLAYER_MOVE:
|
|
// Place selection sprite in center
|
|
TheGame.posx = 3;
|
|
column_select_anim(&cwm, 3, 3, 7);
|
|
cwin_putat_string(&cws, 0, 0, P"PLAYER MOVE", 7);
|
|
break;
|
|
|
|
case GS_COMPUTER_MOVE:
|
|
cwin_putat_string(&cws, 0, 0, P"COMPUTER MOVE", 2);
|
|
break;
|
|
|
|
case GS_PLAYER_WIN:
|
|
cwin_putat_string(&cws, 0, 0, P"PLAYER WINS", 7);
|
|
|
|
// Continue in 4 seconds
|
|
TheGame.count = 240;
|
|
break;
|
|
|
|
case GS_COMPUTER_WIN:
|
|
cwin_putat_string(&cws, 0, 0, P"COMPUTER WINS", 2);
|
|
|
|
// Continue in 4 seconds
|
|
TheGame.count = 240;
|
|
break;
|
|
|
|
case GS_GAME_OVER:
|
|
cwin_putat_string(&cws, 0, 0, P"NO MORE MOVES", 1);
|
|
|
|
// Continue in 4 seconds
|
|
TheGame.count = 240;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Main game loop
|
|
|
|
void game_loop()
|
|
{
|
|
char bx = 0xff, cx;
|
|
|
|
switch (TheGame.state)
|
|
{
|
|
case GS_READY:
|
|
if (!--TheGame.count)
|
|
game_state(GS_PLAYER_MOVE);
|
|
break;
|
|
|
|
case GS_PLAYER_MOVE:
|
|
// Current selection
|
|
cx = TheGame.posx;
|
|
|
|
// Check for key pressed
|
|
if (kbhit())
|
|
{
|
|
// Get key
|
|
char ch = getch();
|
|
|
|
// Numbers 1 to 7 drop immediate
|
|
if (ch >= '1' && ch <= '7')
|
|
bx = cx = ch - '1';
|
|
}
|
|
else
|
|
{
|
|
// Check Joystick
|
|
joy_poll(0);
|
|
|
|
// Move cursor
|
|
if (joyx[0] < 0 && cx > 0)
|
|
cx--;
|
|
else if (joyx[0] > 0 && cx < 6)
|
|
cx++;
|
|
else if(joyb[0])
|
|
bx = cx;
|
|
}
|
|
|
|
// Move selection piece
|
|
column_select_anim(&cwm, TheGame.posx, cx, 7);
|
|
TheGame.posx = cx;
|
|
|
|
if (bx != 0xff)
|
|
{
|
|
// Drop piece into board
|
|
if (board_drop(bx, true))
|
|
{
|
|
// Remember move
|
|
TheGame.moves[TheGame.step++] = bx;
|
|
|
|
// Play drop animation
|
|
item_drop_anim(&cwm, bx, board.ffree[bx], 7);
|
|
|
|
// Show new state of the board
|
|
board_draw_main(&cwm);
|
|
spr_show(0, false);
|
|
|
|
// Check for win condition
|
|
if (board_eval(true) == 32000)
|
|
game_state(GS_PLAYER_WIN);
|
|
else
|
|
game_state(GS_COMPUTER_MOVE);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GS_COMPUTER_MOVE:
|
|
// Check for opening move or calculate next move
|
|
bx = board_opening(TheGame.step, TheGame.moves);
|
|
if (bx == 0xff)
|
|
bx = board_check(0, -32767, 32767, false);
|
|
|
|
// Drop piece into board
|
|
if (board_drop(bx, false))
|
|
{
|
|
// Play drop animation
|
|
item_drop_anim(&cwm, bx, board.ffree[bx], 2);
|
|
|
|
// Show new state of the board
|
|
board_draw_main(&cwm);
|
|
spr_show(0, false);
|
|
|
|
// Check for loss or draw condition
|
|
if (board_eval(false) == 32000)
|
|
game_state(GS_COMPUTER_WIN);
|
|
else if (TheGame.step == 21)
|
|
game_state(GS_GAME_OVER);
|
|
else
|
|
game_state(GS_PLAYER_MOVE);
|
|
}
|
|
break;
|
|
|
|
case GS_PLAYER_WIN:
|
|
case GS_COMPUTER_WIN:
|
|
case GS_GAME_OVER:
|
|
if (!--TheGame.count)
|
|
game_state(GS_READY);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
int main(void)
|
|
{
|
|
mmap_trampoline();
|
|
|
|
// Install character set
|
|
mmap_set(MMAP_RAM);
|
|
memcpy(Font, charset, 2048);
|
|
memcpy(Sprites, spriteset, 256);
|
|
mmap_set(MMAP_NO_BASIC);
|
|
|
|
// Switch screen
|
|
vic_setmode(VICM_TEXT_MC, Screen, Font);
|
|
|
|
spr_init(Screen);
|
|
|
|
// Change colors
|
|
vic.color_border = VCOL_BLUE;
|
|
vic.color_back = VCOL_BLACK;
|
|
vic.color_back1 = VCOL_BLUE;
|
|
vic.color_back2 = VCOL_WHITE;
|
|
|
|
vic.spr_mcolor0 = VCOL_DARK_GREY;
|
|
vic.spr_mcolor1 = VCOL_WHITE;
|
|
|
|
// Clear screen
|
|
memset(Screen, 96, 1000);
|
|
memset(Color, 15, 1000);
|
|
|
|
// Prepare char wins
|
|
cwin_init(&cws, Screen, 5, 0, 30, 1);
|
|
cwin_init(&cwm, Screen, 2, 3, 28, 19);
|
|
cwin_init(&cw, Screen, 30, 6, 8, 7);
|
|
cwin_init(&cwt, Screen, 30, 15, 8, 7);
|
|
|
|
// Init AI
|
|
ai_init();
|
|
|
|
game_state(GS_READY);
|
|
|
|
for(;;)
|
|
{
|
|
game_loop();
|
|
vic_waitFrame();
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|