//---------------------------------------------------------------------------------------------
#include <stdio.h>
#include <system.h>
#include <io.h>
//---------------------------------------------------------------------------------------------
#define IOWR_VGA(base,offset,data) IOWR_16DIRECT(base,(offset)*2,data)
#define IORD_VGA(base,offset) IORD_16DIRECT(base,(offset)*2)
#define PAC_V 1
#define GHOST_V 1
#define PAC_R 9
#define GHOST_R 9

#define INITIAL_GAME_SPEED 5000
#define TOTAL_PACDOT_NUM 240
#define INITIAL_PACX 14
#define INITIAL_PACY 23
#define INITIAL_GHOST_X1 12
#define INITIAL_GHOST_X2 13
#define INITIAL_GHOST_X3 15
#define INITIAL_GHOST_Y 11
#define INITIAL_PAC_LIFE 3
#define PAC_MOUTH_SPEED 21
#define PAC_DIE_TIME 60
#define GHOST_FLOAT_SPEED 20
#define GHOST_WEAK_TIME 600
#define GHOST_BLINK_TIME 30
//---------------------------------------------------------------------------------------------
int gameSpeed = INITIAL_GAME_SPEED;
int pacX = INITIAL_PACX;
int pacY = INITIAL_PACY;
int pacImgX = 203 + 9 * INITIAL_PACX;
int pacImgY = 105 + 9 * INITIAL_PACY;
int pacLife = INITIAL_PAC_LIFE;
int pacMove = 1;
int pacMouth = 0;
int pacDie = 0;
int ghostX[3];
int ghostY[3];
int ghostV[3] = {GHOST_V, GHOST_V, GHOST_V};
int ghostImgX[3];
int ghostImgY[3];
int ghostOption[3][4];
int ghostMove[3];
int ghostOperation[3];
int ghostFloat[] = {0, 0, 0};
int ghostWeakMode[] = {0, 0, 0};
int pacDot = TOTAL_PACDOT_NUM;
int score = 0;
int level = 1;
int gameMode = 0;
unsigned char keyBoardCode = 0;
//---------------------------------------------------------------------------------------------
void createGhost() {
	int i;
	ghostX[0] = INITIAL_GHOST_X1;
	ghostX[1] = INITIAL_GHOST_X2;
	ghostX[2] = INITIAL_GHOST_X3;
	for(i = 0; i < 3; i++) {
		ghostY[i] = INITIAL_GHOST_Y;
		ghostImgX[i] = 203 + 9 * ghostX[i];
		ghostImgY[i] = 105 + 9 * ghostY[i];
		ghostMove[i] = 1;
		ghostV[i] = GHOST_V;
		ghostWeakMode[i] = 0;
	}
}
//---------------------------------------------------------------------------------------------
void initializeStage() {
	IOWR_VGA(VGA_BASE, 14, 0);	//create maze
	IOWR_VGA(VGA_BASE, 12, 645);	//energizers missed in hardware
	IOWR_VGA(VGA_BASE, 13, 27);
	IOWR_VGA(VGA_BASE, 12, 670);
	IOWR_VGA(VGA_BASE, 13, 27);
	if(level < 9) {
		IOWR_VGA(VGA_BASE, 12, 882);	//write level to VGA
		IOWR_VGA(VGA_BASE, 13, 38 + level++);
		if(level > 1)
			gameSpeed -= 250;
	}
	pacDot = TOTAL_PACDOT_NUM;	//initialize game parameters
	pacX = INITIAL_PACX;
	pacY = INITIAL_PACY;
	pacImgX = 203 + 9 * INITIAL_PACX;
	pacImgY = 105 + 9 * INITIAL_PACY;
	pacMove = 1;
	keyBoardCode = 0;
	createGhost();
}
//---------------------------------------------------------------------------------------------
void paintScore() {
	int scoreDigi[4];
	scoreDigi[0] = score / 1000;
	scoreDigi[1] = (score - scoreDigi[0] * 1000) / 100;
	scoreDigi[2] = (score - scoreDigi[0] *  1000 - scoreDigi[1] * 100) / 10;
	scoreDigi[3] = score - scoreDigi[0] *  1000 - scoreDigi[1] * 100 - scoreDigi[2] *  10;
	int i;
	for(i = 0; i < 4; i++) {
		IOWR_VGA(VGA_BASE, 12, 873 + i);
		IOWR_VGA(VGA_BASE, 13, 38 + scoreDigi[i]);
	}
}
//---------------------------------------------------------------------------------------------
void paintPac() {
	IOWR_VGA(VGA_BASE, 0, pacImgX);
	IOWR_VGA(VGA_BASE, 1, pacImgY);
	if(pacMouth < PAC_MOUTH_SPEED / 3) {
		switch(pacMove) {
		case 1:
			IOWR_VGA(VGA_BASE, 2, 1);
			break;
		case 2:
			IOWR_VGA(VGA_BASE, 2, 3);
			break;
		case 3:
			IOWR_VGA(VGA_BASE, 2, 5);
			break;
		case 4:
			IOWR_VGA(VGA_BASE, 2, 7);
			break;
		default:
			break;
		}
	}
	if(pacMouth < 2 * PAC_MOUTH_SPEED / 3) {
		switch(pacMove) {
		case 1:
			IOWR_VGA(VGA_BASE, 2, 2);
			break;
		case 2:
			IOWR_VGA(VGA_BASE, 2, 4);
			break;
		case 3:
			IOWR_VGA(VGA_BASE, 2, 6);
			break;
		case 4:
			IOWR_VGA(VGA_BASE, 2, 8);
			break;
		default:
			break;
		}
	}
	else
		IOWR_VGA(VGA_BASE, 2, 0);
	pacMouth = (pacMouth + 1) % PAC_MOUTH_SPEED;

	paintScore();
}
//---------------------------------------------------------------------------------------------
void paintGhost(int ghostNum) {
	IOWR_VGA(VGA_BASE, 3 + 3 * ghostNum, ghostImgX[ghostNum]);
	IOWR_VGA(VGA_BASE, 4 + 3 * ghostNum, ghostImgY[ghostNum]);
	if(ghostWeakMode[ghostNum] == 0) {
		if(ghostFloat[ghostNum] < GHOST_FLOAT_SPEED / 2)
			IOWR_VGA(VGA_BASE, 5 + 3 * ghostNum, 7 + ghostMove[ghostNum] * 2);
		else
			IOWR_VGA(VGA_BASE, 5 + 3 * ghostNum, 8 + ghostMove[ghostNum] * 2);
	}
	else {
		if(ghostWeakMode[ghostNum] < 3 * GHOST_WEAK_TIME / 4) {
			if(ghostFloat[ghostNum] < GHOST_FLOAT_SPEED / 2)
				IOWR_VGA(VGA_BASE, 5 + 3 * ghostNum, 24);
			else
				IOWR_VGA(VGA_BASE, 5 + 3 * ghostNum, 25);
		}
		else
		{
			if(ghostWeakMode[ghostNum] % GHOST_BLINK_TIME < GHOST_BLINK_TIME / 2) {
				if(ghostFloat[ghostNum] < GHOST_FLOAT_SPEED / 2)
					IOWR_VGA(VGA_BASE, 5 + 3 * ghostNum, 26);
				else
					IOWR_VGA(VGA_BASE, 5 + 3 * ghostNum, 27);
			}
			else {
				if(ghostFloat[ghostNum] < GHOST_FLOAT_SPEED / 2)
					IOWR_VGA(VGA_BASE, 5 + 3 * ghostNum, 24);
				else
					IOWR_VGA(VGA_BASE, 5 + 3 * ghostNum, 25);
			}
		}
		ghostWeakMode[ghostNum] = (ghostWeakMode[ghostNum] + 1) % GHOST_WEAK_TIME;
	}
	ghostFloat[ghostNum] = (ghostFloat[ghostNum] + 1) % GHOST_FLOAT_SPEED;
}
//---------------------------------------------------------------------------------------------
void movePac() {
	int nextPacX;
	int nextPacY;
	unsigned nextContent;
	switch(keyBoardCode)
	{
	case 116:	//right
		if(pacImgX < 446) {
			nextPacX = (pacImgX + PAC_R - 203) / 9;
			IOWR_VGA(VGA_BASE, 15, pacY * 28 + nextPacX);
			nextContent = IORD_VGA(VGA_BASE, 0);
			if(nextContent == 26 || nextContent == 27 || nextContent == 28) {
				pacImgX += PAC_V;
				pacImgY = pacImgY - (pacImgY - 105) % 9;
				pacMove = 1;
			}
			else if(pacMove != 1) {
				switch(pacMove) {
				case 2:
					keyBoardCode = 114;
					break;
				case 3:
					keyBoardCode = 107;
					break;
				case 4:
					keyBoardCode = 117;
					break;
				}
			}
		} else {
			pacImgX = 203;
		}
		break;
	case 114:	//down
		nextPacY = (pacImgY + PAC_R - 105) / 9;
		IOWR_VGA(VGA_BASE, 15, nextPacY * 28 + pacX);
		nextContent = IORD_VGA(VGA_BASE, 0);
		if(nextContent == 26 || nextContent == 27 || nextContent == 28) {
			pacImgY += PAC_V;
			pacImgX = pacImgX - (pacImgX - 203) % 9;
			pacMove = 2;
		}
		else if(pacMove != 2) {
			switch(pacMove) {
			case 1:
				keyBoardCode = 116;
				break;
			case 3:
				keyBoardCode = 107;
				break;
			case 4:
				keyBoardCode = 117;
				break;
			}
		}
		break;
	case 107:	//left
		if(pacImgX >= 203) {
			nextPacX = (pacImgX - PAC_V - 203) / 9;
			IOWR_VGA(VGA_BASE, 15, pacY * 28 + nextPacX);
			nextContent = IORD_VGA(VGA_BASE, 0);
			if(nextContent == 26 || nextContent == 27 || nextContent == 28) {
				pacImgX -= PAC_V;
				pacImgY = pacImgY - (pacImgY - 105) % 9;
				pacMove = 3;
			}
			else if(pacMove != 3) {
				switch(pacMove) {
				case 1:
					keyBoardCode = 116;
					break;
				case 2:
					keyBoardCode = 114;
					break;
				case 4:
					keyBoardCode = 117;
					break;
				}
			}
		} else {
			pacImgX = 446;
		}
		break;
	case 117:	//up
		nextPacY = (pacImgY - PAC_V - 105) / 9;
		IOWR_VGA(VGA_BASE, 15, nextPacY * 28 + pacX);
		nextContent = IORD_VGA(VGA_BASE, 0);
		if(nextContent == 26 || nextContent == 27 || nextContent == 28) {
			pacImgY -= PAC_V;
			pacImgX = pacImgX - (pacImgX - 203) % 9;
			pacMove = 4;
		}
		else if(pacMove != 4) {
			switch(pacMove) {
			case 1:
				keyBoardCode = 116;
				break;
			case 2:
				keyBoardCode = 114;
				break;
			case 3:
				keyBoardCode = 107;
				break;
			}
		}
		break;
	default:
		break;
	}

	pacX = (pacImgX - 203) / 9;
	pacY = (pacImgY - 105) / 9;
	paintPac();

	unsigned mazeContent;
	IOWR_VGA(VGA_BASE, 15, pacY * 28 + pacX);
	mazeContent = IORD_VGA(VGA_BASE, 0);

	switch(mazeContent) {
	case 26:
		IOWR_VGA(VGA_BASE, 12, pacY * 28 + pacX);
		IOWR_VGA(VGA_BASE, 13, 28);
		pacDot--;
		score += 1;
		break;
	case 27:
		IOWR_VGA(VGA_BASE, 12, pacY * 28 + pacX);
		IOWR_VGA(VGA_BASE, 13, 28);
		int i;
		for(i = 0; i < 3; i++)
			ghostWeakMode[i] = 1;
		score += 10;
		break;
	default:
		break;
	}
}
//---------------------------------------------------------------------------------------------
int isAvailable(int ghostNum, int direction) {
	int nextX;
	int nextY;
	unsigned nextContent;
	switch(direction) {
	case 1:	//right
		if(ghostImgX[ghostNum] < 446) {
			nextX = (ghostImgX[ghostNum] + GHOST_R - 203) / 9;
			if((ghostImgY[ghostNum] - 105) % 9 == 0) {
				IOWR_VGA(VGA_BASE, 15, ghostY[ghostNum] * 28 + nextX);
				nextContent = IORD_VGA(VGA_BASE, 0);
				if(nextContent == 26 || nextContent == 27 || nextContent == 28
						|| nextContent == 17 || nextContent == 18)
					return 1;
			}
		}
		else
			return 1;
		break;
	case 2:	//down
		nextY = (ghostImgY[ghostNum] + GHOST_R - 105) / 9;
		if((ghostImgX[ghostNum] - 203) % 9 == 0) {
			IOWR_VGA(VGA_BASE, 15, nextY * 28 + ghostX[ghostNum]);
			nextContent = IORD_VGA(VGA_BASE, 0);
			if(nextContent == 26 || nextContent == 27 || nextContent == 28
					|| nextContent == 17 || nextContent == 18)
				return 1;
		}
		break;
	case 3:	//left
		if(ghostImgX[ghostNum] >= 203) {
			nextX = (ghostImgX[ghostNum] - GHOST_V - 203) / 9;
			if((ghostImgY[ghostNum] - 105) % 9 == 0) {
				IOWR_VGA(VGA_BASE, 15, ghostY[ghostNum] * 28 + nextX);
				nextContent = IORD_VGA(VGA_BASE, 0);
				if(nextContent == 26 || nextContent == 27 || nextContent == 28
						|| nextContent == 17 || nextContent == 18)
					return 1;
			}
		}
		else
			return 1;
		break;
	case 4:	//up
		nextY = (ghostImgY[ghostNum] - GHOST_V - 105) / 9;
		if((ghostImgX[ghostNum] - 203) % 9 == 0) {
			IOWR_VGA(VGA_BASE, 15, nextY * 28 + ghostX[ghostNum]);
			nextContent = IORD_VGA(VGA_BASE, 0);
			if(nextContent == 26 || nextContent == 27 || nextContent == 28
					|| nextContent == 17 || nextContent == 18)
				return 1;
		}
		break;
	default:
		break;
	}
	return 0;
}
//---------------------------------------------------------------------------------------------
int randomMode(int ghostNum) {
	int optionNum = 0;
	//compute all the possible directions at time
	int i;
	for(i = 0; i < 4; i++) {
		ghostOption[ghostNum][i] = isAvailable(ghostNum, i + 1);
		optionNum += ghostOption[ghostNum][i];
	}

	int choice = 0;
	switch(optionNum) {
		case 0:	//no available direction
			return 0;
		case 1:	//choose the only available direction
			for(i = 0; i < 4; i++) {
				if(ghostOption[ghostNum][i] == 1)
					return i + 1;
			}
			break;
			//choose direction according to current moving direction & random choice
		case 2:
		switch(ghostMove[ghostNum]) {
			case 1:
				if(ghostOption[ghostNum][0] == 1)
					return 1;
				else if(ghostOption[ghostNum][1] == 1)
					return 2;
				else
					return 4;
			case 2:
				if(ghostOption[ghostNum][0] == 1)
					return 1;
				else if(ghostOption[ghostNum][1] == 1)
					return 2;
				else
					return 3;
			case 3:
				if(ghostOption[ghostNum][1] == 1)
					return 2;
				else if(ghostOption[ghostNum][2] == 1)
					return 3;
				else
					return 4;
			case 4:
				if(ghostOption[ghostNum][0] == 1)
					return 1;
				else if(ghostOption[ghostNum][2] == 1)
					return 3;
				else
					return 4;
			default:
				break;
			}
		break;
		case 3:
			choice = rand() % 2;
			int badDirection = 0;
			for(i = 0; i < 4; i++) {
				if(ghostOption[ghostNum][i] == 0)
					badDirection = i + 1;
			}
			switch(ghostMove[ghostNum]) {
			case 1:
				if(badDirection == 1) {
					if(choice == 0)
						return 2;
					else
						return 4;
				}
				else if(badDirection == 2) {
					if(choice == 0)
						return 1;
					else
						return 4;
				}
				else {
					if(choice == 0)
						return 1;
					else
						return 2;
				}
			case 2:
				if(badDirection == 1) {
					if(choice == 0)
						return 2;
					else
						return 3;
				}
				else if(badDirection == 2) {
					if(choice == 0)
						return 1;
					else
						return 3;
				}
				else {
					if(choice == 0)
						return 1;
					else
						return 2;
				}
			case 3:
				if(badDirection == 2) {
					if(choice == 0)
						return 3;
					else
						return 4;
				}
				else if(badDirection == 3) {
					if(choice == 0)
						return 2;
					else
						return 4;
				}
				else {
					if(choice == 0)
						return 2;
					else
						return 3;
				}
			case 4:
				if(badDirection == 1) {
					if(choice == 0)
						return 3;
					else
						return 4;
				}
				else if(badDirection == 3) {
					if(choice == 0)
						return 1;
					else
						return 4;
				}
				else {
					if(choice == 0)
						return 1;
					else
						return 3;
				}
			default: break;
			}
			break;
		case 4:
			choice = rand() % 3;
			switch(ghostMove[ghostNum]) {
			case 1:
				if(choice == 0)
					return 1;
				else if(choice == 1)
					return 2;
				else
					return 4;
			case 2:
				if(choice == 0)
					return 1;
				else if(choice == 1)
					return 2;
				else
					return 3;
			case 3:
				if(choice == 0)
					return 2;
				else if(choice == 1)
					return 3;
				else
					return 4;
			case 4:
				if(choice == 0)
					return 1;
				else if(choice == 1)
					return 3;
				else
					return 4;
			default: break;
			}
			break;
			default: break;
	}
	return 0;
}
//---------------------------------------------------------------------------------------------
void moveGhost(int ghostNum) {
	ghostOperation[ghostNum] = randomMode(ghostNum);
	switch(ghostOperation[ghostNum]) {
	case 1:	//right
		if(ghostImgX[ghostNum] < 446) {
			if(ghostWeakMode[ghostNum] % 2 == 0) {
				ghostImgX[ghostNum] += ghostV[ghostNum];
				ghostMove[ghostNum] = 1;
			}
		}
		else {
			ghostImgX[ghostNum] = 203;
			ghostMove[ghostNum] = 1;
		}
		break;
	case 2:	//down
		if(ghostWeakMode[ghostNum] % 2 == 0) {
			ghostImgY[ghostNum] += ghostV[ghostNum];
			ghostMove[ghostNum] = 2;
		}
		break;
	case 3: //left
		if(ghostImgX[ghostNum] >= 203) {
			if(ghostWeakMode[ghostNum] % 2 == 0) {
				ghostImgX[ghostNum] -= ghostV[ghostNum];
				ghostMove[ghostNum] = 3;
			}
		}
		else {
			ghostImgX[ghostNum] = 446;
			ghostMove[ghostNum] = 3;
		}
		break;
	case 4: //up
		if(ghostWeakMode[ghostNum] % 2 == 0) {
			ghostImgY[ghostNum] -= ghostV[ghostNum];
			ghostMove[ghostNum] = 4;
		}
		break;
	default:
		break;
	}

	ghostX[ghostNum] = (ghostImgX[ghostNum] - 203) / 9;
	ghostY[ghostNum] = (ghostImgY[ghostNum] - 105) / 9;
	paintGhost(ghostNum);
}
//---------------------------------------------------------------------------------------------
void startPac() {
	initializeStage();
	printf("PacMan is running!\n");
	srand(time(NULL));
	while(pacLife >= 0)
	{
		if(gameMode == 0) {
			while(!IORD_8DIRECT(KEYBOARD_BASE,0)){
				int i;
				for(i = 0; i < gameSpeed; i++);
				movePac();
				for(i = 0; i < 3; i++)
					moveGhost(i);
				if(pacDot == 0) {
					int i;
					for(i = 0; i < 10 * gameSpeed; i++);
					initializeStage();
				}
			}
			keyBoardCode = IORD_8DIRECT(KEYBOARD_BASE,1);
			if(keyBoardCode == 118) {
				gameMode = 1;
				keyBoardCode = 0;
			}
			else{
				movePac();
				int i;
				for(i = 0; i < 3; i++)
					moveGhost(i);
				if(pacDot == 0) {
					int i;
					for(i = 0; i < 10 * gameSpeed; i++);
					initializeStage();
				}
			}

		}
		else if(gameMode == 1) {
			while(!IORD_8DIRECT(KEYBOARD_BASE,0));
			keyBoardCode = IORD_8DIRECT(KEYBOARD_BASE,1);
			if(keyBoardCode == 118)
				gameMode = 0;
		}
		printf("%d\n", gameMode);
	}
}
//---------------------------------------------------------------------------------------------
int main() {
	startPac();
	return 0;
}


