Archived

This topic is now archived and is closed to further replies.

DevLiquidKnight

Coding a more realistic yet very effective AI for pong?

Recommended Posts

I made a game in pong using flash.. I coulda done it in C++ but this is for my website I wanted an online game anyways. I am trying to find a way that I could make the AI for the computer more humaistic yet a very good human. So far the AI is 100% perfect its kinda hard to score. Does anyone have any references to making the AI smart but not PERFECT. Or anything else?

Share this post


Link to post
Share on other sites
To make a perfect AI you could just have it calculate the vertical velocity of the ball and have the paddle match it exactly (and update the paddles velocity every time the ball collided with something). To make it more human, you could give the AI a probability of matching the ball velocity exactly, using a normal distribution. So there is say a 60% chance the paddle velocity will be within 5% of the balls velocity, 95% chance that the paddle will be within 15% of the balls velocity, etc (those are just random percentages I chose, but you get the idea hopefully).

Share this post


Link to post
Share on other sites
I wrote a pong game a long time ago in high school. The AI was very simple, but seemed to play well. Basically the ball moved 1 pixel horizontally per frame, and 2 pixels vertically per frame. The computer player only moves when the ball is moving towards it, and the logic there basically goes:


// think in terms of x,y coordinates here
if (ball''s y value < computer paddle''s y value)
computer_paddle_y_value -= 2;
else if (ball''s y value > computer paddle''s y value)
computer_paddle_y_value += 2;


I also had a kind of "slamming" (I guess that''s what you''d call it), where if you were moving your paddle in the same direction as the ball when the ball hit your paddle, the ball would move twice as fast. You had to do this to beat the computer. Even when the ball was moving twice as fast, it still took a few times back and forth to beat it.

I found this on an old floppy disk I had. It''s basically a windows version of the DOS program I wrote. This was my first real windows program, I think (besides stupid hello world stuff). It compiled the first time and ran under VC6, so, give it a try and you''ll get an idea of how it feels. Use the arrow keys (up and down) to move your paddle. I can upload an executable if you can''t get it to compile.


#define WIN32_LEAN_AND_MEAN
#define KEYDOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000 ? 1: 0)
#define KEYUP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000 ? 0 : 1)

#include <windows.h>
#include <windowsx.h>
#include <wingdi.h>
#include <stdio.h>

// Globals

RECT rc;
HDC hdcMem;
HBITMAP hbmMem, hbmOld;

HBRUSH black_brush = CreateSolidBrush(RGB(0,0,0));
HBRUSH white_brush = CreateSolidBrush(RGB(255,255,255));
HBRUSH blue_brush = CreateSolidBrush(RGB(50,50,255));
HBRUSH red_brush = CreateSolidBrush(RGB(255,0,0));
HPEN yellow_pen = CreatePen(PS_SOLID, 2,RGB(255,255,0));
HPEN black_pen = CreatePen(PS_SOLID, 1, RGB(0,0,0));
HPEN yellow_dash = CreatePen(PS_DOT, 1, RGB(255,255,0));
HPEN white_pen = CreatePen(PS_SOLID, 1, RGB(255,255,255));

HDC hdc;
HWND hwnd;

DWORD start_time, fps_timer;
int frames = 0;

int pad1, pad2;
int ballx = 450, bally, ball_size = 4;
int xinc = -20, yinc = 10;
int score1 = 0, score2 = 0;
int players;
int next;

char buffer[80];

// Functions


void Init();
void GameMain();
void NewRound();
void Score(int p);
void DrawBoard();
void Copy();


LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
PAINTSTRUCT ps;
HDC hdc;

switch(msg)
{
case WM_CREATE:
{
return(0);
} break;

case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
EndPaint(hwnd, &ps);
return(0);
} break;
case WM_CLOSE:
{
return(DefWindowProc(hwnd, msg, wparam, lparam));
} break;

case WM_DESTROY:
{
PostQuitMessage(0);
return(0);
} break;
default: break;
}

return(DefWindowProc(hwnd, msg, wparam, lparam));
}


int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int nshowcmd)
{

WNDCLASSEX winclass;
MSG msg;

winclass.cbSize = sizeof(WNDCLASSEX);
winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
winclass.lpfnWndProc = WindowProc;
winclass.cbClsExtra = 0;
winclass.cbWndExtra = 0;
winclass.hInstance = hinstance;
winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
winclass.hCursor = LoadCursor(NULL, IDC_ARROW);
winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
winclass.lpszMenuName = NULL;
winclass.lpszClassName = "WINCLASS1";
winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

if(!RegisterClassEx(&winclass))
return(0);
if(!(hwnd = CreateWindowEx(NULL, "WINCLASS1", "Pong!", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 200-56,150-42,408+112,327+84,NULL, NULL, hinstance, NULL)))
return(0);

hdc = GetDC(hwnd);

Init();
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, 0xffffff);
TextOut(hdc, 0,0,"1 or 2 players?", strlen("1 or 2 players?"));

while(!players)
{
if(KEYDOWN(0x31)))
players = 1;
else if(KEYDOWN(0x32)))
players = 2;
};

NewRound();
while(TRUE)
{
if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
if(msg.message == WM_QUIT)
break;

TranslateMessage(&msg);
DispatchMessage(&msg);
}

GameMain();
}
DeleteObject(hbmMem);
DeleteDC(hdcMem);

ReleaseDC(hwnd,hdc);
return(msg.wParam);
}

void Score(int p)
{
if(p == 1)
score1++;
else if(p == 2)
score2++;
if(score1 > 4 || score2 > 4)
{
pad1 = 160, pad2 = 160;
bally = 192;

DrawBoard();

}


if(score1 > 4)
TextOut(hdcMem, 208, 184, "Player 1 Wins!", strlen("Player 1 Wins!"));
else if(score2 > 4)
TextOut(hdcMem, 208, 184, "Player 2 Wins!", strlen("Player 2 Wins!"));

if(score1 > 4 || score2 > 4)
{
start_time = GetTickCount();
Copy();
while((GetTickCount() - start_time) < 1250) {};
SendMessage(hwnd, WM_CLOSE, 0,0);
}
else
NewRound();
}

void Copy()
{
BitBlt(hdc, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, hdcMem, 0, 0, SRCCOPY);
}

void DrawBoard()
{
FillRect(hdcMem, &rc, black_brush);

SetBkMode(hdcMem, TRANSPARENT);
SetTextColor(hdcMem,0xffffff);
sprintf(buffer,"%d",score1);
TextOut(hdcMem, 100+28,50+21,buffer,strlen(buffer));
sprintf(buffer,"%d",score2);
TextOut(hdcMem, 384,50+21,buffer,strlen(buffer));

SelectObject(hdcMem, red_brush);
Rectangle(hdcMem,0,pad1,15,pad1+64);

SelectObject(hdcMem, blue_brush);
Rectangle(hdcMem, 388+109, pad2, 512,pad2+64);

SelectObject(hdcMem, white_brush);
SelectObject(hdcMem, white_pen);
Rectangle(hdcMem, ballx-ball_size, bally - ball_size,
ballx + ball_size, bally + ball_size);

SelectObject(hdcMem, yellow_pen);
MoveToEx(hdcMem,0,1,NULL);
LineTo(hdcMem, 400+112,1);

MoveToEx(hdcMem,0,299+84,NULL);
LineTo(hdcMem, 400+112,299+84);

SelectObject(hdcMem, yellow_dash);
MoveToEx(hdcMem,200+56,0,NULL);
LineTo(hdcMem,200+56,300+84);
SelectObject(hdcMem, black_pen);

SetBkMode(hdcMem, TRANSPARENT);
SetTextColor(hdcMem, 0xffffff);

start_time = GetTickCount();


}

void GameMain()
{
if(KEYDOWN(VK_DOWN)))
{
pad2+=10;
}
if(KEYDOWN(VK_UP)))
{
pad2-=10;
}
if(players == 2)
{
if(KEYDOWN(0x41)))
pad1-=10;
if(KEYDOWN(0x5a)))
pad1+=10;
}

if(players == 1 && xinc < 0)
{
if(bally < pad1 + 32)
pad1 -= 10;
if(bally > pad1 + 32)
pad1 += 10;
}

//pad2 = bally-32;



if(pad1 < 1)
pad1 = 1;
if(pad1 >320)
pad1 = 320;
if(pad2 < 1)
pad2 = 1;
if(pad2 > 320)
pad2 = 320;

FillRect(hdcMem, &rc, black_brush);


ballx += xinc;
bally += yinc;
if(ballx < 20)
{
if(bally < pad1-4 || bally > pad1+68)
{
ballx = 450;
Score(2);
NewRound();
}
else
{
ballx = 19;
xinc *= -1;
if(KEYDOWN(0x5a)))
{
if(yinc == 5)
{
yinc = 10;
xinc = -20;
}
else if(yinc == -10 && pad1 < 319)
{
yinc = 5;
xinc = -10;
}
}
if(KEYDOWN(0x41)))
{
if(yinc == -5)
{
yinc = -10;
xinc = -20;
}
else if(yinc == 10 && pad1 > 2)
{
yinc = -5;
xinc = -10;
}
}
}
}
if(ballx > 492)
{
if(bally < pad2-4 || bally > pad2+68)
{
ballx = 62;
Score(1);
NewRound();
}
else
{
ballx = 493;
xinc *=-1;
if(KEYDOWN(VK_DOWN)))
{
if(yinc == 5)
{
yinc = 10;
xinc = -20;
}
else if(yinc == -10 && pad2 < 319)
{
yinc = 5;
xinc = -10;
}
}
if(KEYDOWN(VK_UP)))
{
if(yinc == -5)
{
yinc = -10;
xinc = -20;
}
else if(yinc == 10 && pad2 > 2)
{
yinc = -5;
xinc = -10;
}
}
}
}

if(bally < 5)
{
bally = 5;
yinc *= -1;
}
if(bally > 379)
{
bally = 379;
yinc *=-1;
}
//display score

SetBkMode(hdcMem, TRANSPARENT);
SetTextColor(hdcMem,0xffffff);
sprintf(buffer,"%d",score1);
TextOut(hdcMem, 100+28,50+21,buffer,strlen(buffer));
sprintf(buffer,"%d",score2);
TextOut(hdcMem, 384,50+21,buffer,strlen(buffer));

SelectObject(hdcMem, red_brush);
Rectangle(hdcMem,0,pad1,15,pad1+64);

SelectObject(hdcMem, blue_brush);
Rectangle(hdcMem, 388+109, pad2, 512,pad2+64);

SelectObject(hdcMem, white_brush);
SelectObject(hdcMem, white_pen);
Rectangle(hdcMem, ballx-ball_size, bally - ball_size,
ballx + ball_size, bally + ball_size);
double fps;

SelectObject(hdcMem, yellow_pen);
MoveToEx(hdcMem,0,1,NULL);
LineTo(hdcMem, 400+112,1);

MoveToEx(hdcMem,0,299+84,NULL);
LineTo(hdcMem, 400+112,299+84);

SelectObject(hdcMem, yellow_dash);
MoveToEx(hdcMem,200+56,0,NULL);
LineTo(hdcMem,200+56,300+84);
SelectObject(hdcMem, black_pen);

if(GetTickCount() - fps_timer > 1000)
fps = (frames/((GetTickCount() - fps_timer)/1000));
sprintf(buffer, "%f",fps);
//TextOut(hdcMem,0,0,buffer,strlen(buffer));


while((GetTickCount() - start_time) < 25) {};
BitBlt(hdc, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, hdcMem, 0, 0, SRCCOPY);
frames++;

start_time = GetTickCount();

SelectObject(hdcMem, hbmOld);
if(KEYDOWN(VK_ESCAPE)))
SendMessage(hwnd, WM_CLOSE, 0,0);
}

void NewRound()
{
pad1 = 160, pad2 = 160;
bally = 192;
if(xinc > 10)
xinc = 10;
else if(xinc < -10)
xinc = -10;
if(yinc > 5)
yinc = 5;
else if(yinc < -5)
yinc = -5;
DrawBoard();
TextOut(hdcMem, 208, 184, "Get Ready...", strlen("Get Ready..."));
Copy();
while((GetTickCount() - start_time) < 750)
{
if(KEYDOWN(VK_ESCAPE)))
SendMessage(hwnd, WM_CLOSE, 0,0);
};
start_time = GetTickCount();
frames = 0;
fps_timer = GetTickCount();

}

void Init()
{
GetClientRect(hwnd, &rc);
hdcMem = CreateCompatibleDC(hdc);
hbmMem = CreateCompatibleBitmap(hdc, rc.right-rc.left, rc.bottom-rc.top);
SelectObject(hdcMem, hbmMem);
hbmOld = hbmMem;

}

Share this post


Link to post
Share on other sites
Here''s a perfect AI for pong...

PCPaddle.Y = Ball.Y; 


...well... yeah :D

You could probably fake perception and anticipation by modifying the paddle''s position by n pixels from the ball''s exact y position and reducing n as it approaches. If it originally "guessed" too high/low or the ball were moving too fast, it wouldn''t be able to close the gap between it''s guess and the real y position of the ball and would lose.

I''m basing myself on the fact a lot of people will (bear with me) draw an imaginary horizontal line between their paddle and the ball and try to follow the ball or align themselves with that line when the ball nears their side. Since we''re not always very accurate with stuff like that, particularly if the ball is far from our side, we can make mistakes and end up missing it.

Not sure if this would help...

Alternatively, though it would probably be a waste, you could try rigging up a simple NN taking the ball''s position and speed as inputs and train it in real-time. You''d have an opponent getting progressively better but... eh... why bother? Other than to brag about having an all-mighty, magical NN in your code?

Share this post


Link to post
Share on other sites
I would add some randomness to where it predicts where the ball will be. This is more the way humans make mistakes.

You can easily trace the path of the ball to the computer''s goal line to see where it needs to be to hit the ball. Have it wait in that position, but throw a bit of randomness in there. Rather than waiting right on that spot maybe it is usually on that spot, but it has a normal distrobution from that curve, so it can be away from it. Then when the ball is comming have it adjust slightly to try and match it.

I think this would be the most believable, and you can pick some parameters to give it different difficulty settings.

Share this post


Link to post
Share on other sites
To make a simple AI, just run a loop when the ball is hit by the player that will simulate (in math only) the movement of the ball up to the point where the computer paddle will need to hit it. Then calculate how long it will be before the ball will get to that point, and you have a velocity for the paddle of (PlaceToBe-PaddlePosition)/TimeBallTakesToGetThere

If you include some random amount in calculating either the ball trajectory or the time it takes to get there, you have an imperfect velocity. Depending on the amount of error, the paddle might be wide enough to still hit the ball.

Share this post


Link to post
Share on other sites
Here is my idea for a computer ''AI'' for pong.

Ideally, I want the computer to play like a computer, so what we do is give weaker opponents less precision in their fixed / floating point calcuations.

ie:
#define dumbPlayer 10
#define densePlayer 100
#define okPlayer 1000
#define goodPlayer 10000
#define greatPlayer 100000

float realValue = sin(angle);
int tempValue = realValue * goodPlayer;
float outValue = ( tempValue / (float)goodPlayer);

See what happens? We specifiy the level of precision used during calculations by the AI. When you start adding together the approximated values the round-off error will accumulate, and the AI will be off.

Swaping goodPlayer with dumbPlayer will make the calcuations less accurate. If sin(angle) is 0.958934234, goodPlayer will get 0.9589, while dumbPlayer would simply get 0.9.

This way your dumb AI should be able to make some shots, but not all of them, and your really good AI should be able to most (if not all) of them.

Good luck,
Will

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Well, I''ve done pong clone game with one very sophisticated AI :-) Game''s freeware and you can download source code at www.pongowar.prv.pl. You should take a look at cpu.cpp.
Oh, and game''s name is Pongowar :-)

Share this post


Link to post
Share on other sites
You could use your "perfect" AI (you metioned that it was very difficult to score) as a base, but increase a "distortion" variable a tiny amount every game loop which the AI then uses to become more and more inaccurate as time progreses - therby simulating AI that gets tired.

Share this post


Link to post
Share on other sites