Inconsistent Behaviour Between 2 Players of Game - c++

I've been making a game for an English presentation (I know right?) and I've been having a few weird problems recently.
At this stage, there are two squares for the players that can fire bullets. When the bullets hit the side of the screen or the middle border, they should disappear and possibly be reused later.
The problem is, first of all, when either player shoots up or down, the bullets disappear into the top/bottom/middle of the screen like they're supposed to. If player 2 shoots a few to the side, as soon as one hits, they seem to all disappear (only player 2's bullets) and I have to wait a few seconds before it starts shooting again.
The main problem though, is that even when I use the exact same code modified for player 1 instead of player 2, when player 1's first shot to hit the side of the screen gets there, it segfaults the program.
The REALLY odd thing is that at one point, this happened, and without changing anything, I ran it again and it all worked perfectly fine. It might have to do with the order of bullets between player 1 and 2, or even where I shoot first. I've tried recreating it, but no results yet.
Just a note before you try to compile the code, I used a wrapper I made to make the window with ease. I noted down what goes on behind the scenes with /// comments though, so adding that info into whatever method you use to make your windows will work just as well.
Problem areas are listed near the bottom:
///Works best on 1280x1024 resolution
///1 vs 1 splitscreen game that involves flying around and shooting things
///angles start at 0 facing upwards and increase clockwise
#include <window.h> //incomplete wrapper, but works perfectly for quick, easy window creation
#define _USE_MATH_DEFINES //for M_PI
#include <cmath> //for M_PI
#include <iostream> //used for debugging
using std::cout; //output
struct Actions //actions a player can take
{
bool up; //if player is moving in these 4 directions, they will be true
bool left;
bool down;
bool right;
bool shoot; //if player is shooting, this will be true
};
struct Player //a player
{
Player() {};
void fire(); //fire a bullet
void checkActions (HWND); //check what actions player is taking
double x; //position (centre of square)
double y;
double angle; //angle (might add diagonals so...)
int pnum; //player number (0 or 1)
COLORREF colour; //player's colour
Actions action; //player's actions
};
struct Bullet //a bullet
{
double x; //position (centre of square)
double y;
Player owner; //owner of bullet
int index; //bullet's index in array
double angle; //bullet's angle
};
Player *p = new Player[2]; //2 players
Bullet **bullet; //2d array of bullets
int bcount[2] = {0}; //number of bullets for each player
int btime [2] = {0}; //timer for bullets
const double PLSIZE = 10; //player size = 20x20 square (10 from centre outwards)
const double BSIZE = 2; //bullet size = 4x4 square
const double SPEED = 1; //player's moving speed is 1
const int BDELAY = 100; //delay between bullets is 100ms
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); //window procedure
void OnPaint (HDC, HWND); //painting function
void moveBullets (HWND); //calculates bullet positions
void deleteBullet (int, int); //"deletes" a bullet
int main() //main function
{
//hide(console()); //hides console window (currently showing for debugging)
bullet = new Bullet*[2]; //create bullet array of 1000/player (I'll size down the 1000 later)
bullet[0] = new Bullet[1000];
bullet[1] = new Bullet[1000];
p[0].x = 630; //player 1's position
p[0].y = 250;
p[0].colour = RGB(255,0,0); //player 1 is red
p[0].pnum = 0; //player 1's number is 0
p[0].angle = 0; //face upwards
p[0].action = {0}; //player 1 is doing nothing
p[1].x = 630; //player 2's position
p[1].y = 750;
p[1].colour = RGB(0,0,255); //player 2 is blue
p[1].pnum = 1; //player 2's number is 1
p[1].angle = 0; //face upwards
p[1].action = {0}; //player 2 is doing nothing
Window window; //create window object (part of wrapper, sets default values for class and window)
///background = (HBRUSH)COLOR_WINDOW
///class name = "Default Wrapper Class"
///hInstance = GetModuleHandle (NULL)
///all others are standard default or 0
window.createClass(WndProc); //create class using earlier-mentioned window procedure
window.setStyle(WS_OVERLAPPEDWINDOW | WS_MAXIMIZE); //set window style to overlapped and maximized
window.setTitle (L"Word Blaster"); //set window title to "Word Blaster" (it's an English project, shush)
///x/y/width/height = CW_USEDEFAULT
///class name = other class name
///hInstance = GetModuleHandle (NULL)
///all others are standard default or 0
HWND hwnd = window.createWindow(); //create window
MSG msg; //message loop
while(GetMessage(&msg,0,0,0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) //window proc
{
HDC hdc; //hdc for painting
PAINTSTRUCT ps; //paintstruct for painting
bool ret = false; //return value (you'll see later)
switch(msg)
{
case WM_CREATE: //currently not in use
break;
case WM_KEYDOWN: //check for pressed keys
switch (wParam) //keycode
{
case 0x57: //'w'
p[0].action.up = true; //player 1 wants to move up (couldn't just change position here or no diagonal movement)
break;
case 0x41: //'a', left
p[0].action.left = true;
break;
case 0x53: //'s', down
p[0].action.down = true;
break;
case 0x44: //'d', right
p[0].action.right = true;
break;
case 0x20: // space, shoot
p[0].action.shoot = true;
break;
case VK_UP: //up arrow, player 2 up
p[1].action.up = true;
break;
case VK_LEFT: //left arrow
p[1].action.left = true;
break;
case VK_DOWN: //down arrow
p[1].action.down = true;
break;
case VK_RIGHT: //right arrow
p[1].action.right = true;
break;
case VK_RETURN: //either enter key, p2 shoot
p[1].action.shoot = true;
break;
}
break;
case WM_KEYUP: //check for unpressed keys
switch (wParam)
{
case 0x57: //'w', player 1 should stop moving up
p[0].action.up = false;
break;
case 0x41: //all same order as above
p[0].action.left = false;
break;
case 0x53:
p[0].action.down = false;
break;
case 0x44:
p[0].action.right = false;
break;
case 0x20: // space
p[0].action.shoot = false;
break;
case VK_UP:
p[1].action.up = false;
break;
case VK_LEFT:
p[1].action.left = false;
break;
case VK_DOWN:
p[1].action.down = false;
break;
case VK_RIGHT:
p[1].action.right = false;
break;
case VK_RETURN:
p[1].action.shoot = false;
break;
}
break;
case WM_PAINT: //draw on screen
hdc = BeginPaint (hwnd, &ps); //prepare window for drawing
OnPaint (hdc,hwnd); //draw
EndPaint (hwnd, &ps); //finish drawing
break;
case WM_CLOSE: //if ready to close
show(console()); //show console window in case it doesn't close
end(); //close console window (PostMessage (GetConsoleWindow(),WM_CLOSE,0,0))
DestroyWindow(hwnd); //close main window
break;
case WM_DESTROY: //window is closing
PostQuitMessage(0); //post WM_QUIT to end (probably won't get here since console closes earlier)
break;
case WM_ERASEBKGND: //if background is going to be erased, don't let it (causes flicker)
ret = true; //hold that thought for a bit
break;
}
p[0].checkActions(hwnd); //check player 1's actions
p[1].checkActions(hwnd); //check player 2's actions
moveBullets (hwnd); //move any bullets
InvalidateRect (hwnd,NULL,true); //update window
Sleep (1); //delay a bit
if (!ret) return DefWindowProc(hwnd, msg, wParam, lParam); //if WM_ERASEBKGND wasn't called, take default action
}
void Player::fire() //fire a bullet
{
bullet [pnum][bcount[pnum]].x = x; //bullet starts in player's centre
bullet [pnum][bcount[pnum]].y = y;
bullet [pnum][bcount[pnum]].owner = *this; //owner of bullet is the object calling this function
bullet [pnum][bcount[pnum]].index = bcount[pnum]; //index of bullet is the number of bullets for player
bullet [pnum][bcount[pnum]].angle = angle; //angle of bullet is player's angle
while (
(bullet[pnum][bcount[pnum]].x - BSIZE < x + PLSIZE && bullet[pnum][bcount[pnum]].x - BSIZE > x - PLSIZE //left side of bullet inside player OR
|| bullet[pnum][bcount[pnum]].x + BSIZE < x + PLSIZE && bullet[pnum][bcount[pnum]].x + BSIZE > x - PLSIZE) //right side in player --- AND ---
&& (bullet[pnum][bcount[pnum]].y - BSIZE < y + PLSIZE && bullet[pnum][bcount[pnum]].y - BSIZE > y - PLSIZE //top in player OR
|| bullet[pnum][bcount[pnum]].y + BSIZE < y + PLSIZE && bullet[pnum][bcount[pnum]].y + BSIZE > y - PLSIZE) //bottom in player
)
{
bullet[pnum][bcount[pnum]].x += sin (bullet[pnum][bcount[pnum]].angle * M_PI / 180); //start moving bullet until it's out
bullet[pnum][bcount[pnum]].y -= cos (bullet[pnum][bcount[pnum]].angle * M_PI / 180);
}
btime [pnum] = GetTickCount(); //set up bullet delay for that player
++bcount[pnum]; //increase number of bullets for that player
}
void Player::checkActions (HWND hwnd) //check player's actions
{
RECT r;
GetClientRect (hwnd, &r); //get canvas space
if (action.up) //if moving up
{
y -= SPEED; //change y position
angle = 0; //change angle
if (pnum == 0) //if player 1
{
if (y - PLSIZE < 1) y = PLSIZE + 1; //check top of screen boundary
}
else //if player 2
{
if (y - PLSIZE < r.bottom / 2 + 5) y = r.bottom / 2 + 5 + PLSIZE; //check middle boundary
}
}
if (action.left) //if moving left
{
x -= SPEED; //change x position
angle = 270; //change angle
if (x - PLSIZE < 1) x = PLSIZE + 1; //check left of screen boundary
}
if (action.down) //down is opposite of up
{
y += SPEED;
angle = 180;
if (pnum == 0)
{
if (y + PLSIZE > r.bottom / 2 - 5) y = r.bottom / 2 - 5 - PLSIZE;
}
else
{
if (y + PLSIZE > r.bottom) y = r.bottom - PLSIZE;
}
}
if (action.right) //right is opposite of left
{
x += SPEED;
angle = 90;
if (x + PLSIZE > r.right) x = r.right - PLSIZE;
}
if (action.shoot && GetTickCount() - btime [pnum] > BDELAY) fire(); //if player wants to shoot and enough time has passed, fire bullet
}
void OnPaint (HDC hdc, HWND hwnd) //draw stuff
{
RECT r;
GetClientRect (hwnd, &r); //get canvas area
HDC buffer = CreateCompatibleDC (hdc); //create buffer DC
HBITMAP bitmap = CreateCompatibleBitmap (hdc,r.right,r.bottom); //create buffer bitmap
HBITMAP oldBM = (HBITMAP)SelectObject (buffer, bitmap); //create another bitmap
HBRUSH player1brush = CreateSolidBrush(p[0].colour); //player 1's brush
HBRUSH player2brush = CreateSolidBrush(p[1].colour); //player 2's brush
HBRUSH blackBrush = CreateSolidBrush (RGB(0,0,0)); //black brush
HPEN /*player1*/pen = CreatePen (PS_NULL,1,RGB(255,0,0)); //don't need pen
BitBlt(buffer,0,0,r.right,r.bottom,NULL,0,0,WHITENESS); //erase bitmap background
SelectObject(buffer,pen); //select pen (since I need one to do anything)
SelectObject (buffer, blackBrush); //select black brush
Rectangle (buffer, 0, r.bottom / 2 - 5, r.right, r.bottom / 2 + 5); //draw middle line
// MoveTo () //these comments are because I was about to change the graphics to ships
SelectObject (buffer,player1brush); //select player 1's brush
Rectangle (buffer,p[0].x-PLSIZE,p[0].y-PLSIZE,p[0].x+PLSIZE,p[0].y+PLSIZE); //draw player 1
SelectObject (buffer,player2brush); //do the same for p2
Rectangle (buffer,p[1].x-PLSIZE,p[1].y-PLSIZE,p[1].x+PLSIZE,p[1].y+PLSIZE);
if (bcount[0] > 0) //if p1 has a bullet
{
SelectObject (buffer, blackBrush); //select black brush
for (int i = 0; i < bcount[0]; ++i) //draw bullet(s)
{
Ellipse (buffer, bullet [0][i].x - BSIZE, bullet [0][i].y - BSIZE, bullet [0][i].x + BSIZE, bullet [0][i].y + BSIZE);
}
}
if (bcount[1] > 0) //same goes for p2
{
SelectObject (buffer, blackBrush);
for (int i = 0; i < bcount[1]; ++i)
{
Ellipse (buffer, bullet [1][i].x - BSIZE, bullet [1][i].y - BSIZE, bullet [1][i].x + BSIZE, bullet [1][i].y + BSIZE);
}
}
BitBlt(hdc, 0,0, r.right , r.bottom, buffer, 0,0, SRCCOPY); //copy buffer bitmap to window
DeleteObject (player1brush); //delete stuff
DeleteObject (player2brush);
DeleteObject (pen);
SelectObject (buffer, oldBM);
DeleteObject (bitmap);
DeleteDC(buffer);
}
void moveBullets (HWND hwnd) //move the bullets ***PROBLEM AREA***
{
RECT r;
GetClientRect (hwnd, &r); //get canvas area
if (bcount[0] > 0) //if p1 has bullet(s)
{
for (int i = 0; i < bcount[0]; ++i) //go through p1's bullets
{
///DOESN'T WORK
bullet [0][i].x += sin (bullet [0][i].angle * M_PI / 180); //move the bullet horizontally
if (bullet [0][i].x - BSIZE < 1 || bullet [0][i].x + BSIZE > r.right) //if out of bounds
{
deleteBullet (0, bullet [0][i].index); //delete the bullet
--i; //if bullet [2] was deleted, bullet [2] will now be the old bullet [3] so recheck this one next time
}
///WORKS PERFECTLY
bullet [0][i].y -= cos (bullet [0][i].angle * M_PI / 180); //do same for y, including middle border
if (bullet [0][i].y - BSIZE < 1 || bullet [0][i].y + BSIZE > r.bottom / 2 - 5)
{
deleteBullet (0, bullet [0][i].index);
--i;
}
}
}
if (bcount[1] > 0) //exact same thing (I checked a LOT) for p2
{
for (int i = 0; i < bcount[1]; ++i)
{
///WORKS PERFECTLY (at least in the p1 sense, there is a slight problem)
bullet [1][i].x += sin (bullet [1][i].angle * M_PI / 180);
if (bullet [1][i].x - BSIZE < 1 || bullet [1][i].x + BSIZE > r.right)
{
deleteBullet (1, bullet [1][i].index);
--i;
}
///WORKS PERFECTLY
bullet [1][i].y -= cos (bullet [1][i].angle * M_PI / 180);
if (bullet [1][i].y - BSIZE < r.bottom / 2 + 5 || bullet [1][i].y + BSIZE > r.bottom)
{
deleteBullet (1, bullet [1][i].index);
--i;
}
}
}
}
void deleteBullet (int player, int index) //delete bullet ***PROBLEM AREA***
{
if (index != bcount [player] - 1) //if it isn't the last bullet
{
for (int j = index; j < bcount[player] - 1; ++j) //go from here to the end of the current bullets - 1
{
bullet [player][j] = bullet [player][j+1]; //copy the next bullet into this spot
--bullet [player][j].index; //change the index of the bullet since it was moved back one
}
}
--bcount [player]; //lessen the bullet count, this is all that's needed if it's the last bullet
}
Any help with said problems or with something else you notice would be greatly appreciated.
EDIT: one side thing I forgot to ask was if there was a better way to continuously do things like move bullets for example than to put all that outside of the switch in the window procedure and the DefWindowProc after it.

It looks like deleteBullet may get called twice on the same i inside the loop for (i=0...) in moveBullets. I think this could happen if the bullet is moving diagonally. I'm not sure it's the cause of the trouble, though.
First English question I've seen on stackoverflow!

Related

in Vsual Studio C++ OffsetRgn not working>

void CBallBounceView::OnDraw(CDC* pDC)
{
CBallBounceDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
CString str;
str.Format(_T("Counter = %d"), counter);
CRgn rgnA,copyeed;
VERIFY(rgnA.CreateEllipticRgn(50, 50, 150, 150));
CBrush brA;
VERIFY(brA.CreateSolidBrush(RGB(255, 0, 0)));
VERIFY(pDC->FillRgn(&rgnA, &brA)); // rgnA Red Filled
int nOffsetResult = rgnA.OffsetRgn(500, 100);
ASSERT(nOffsetResult != ERROR && nOffsetResult != NULLREGION);
}
I am trying to offset the ellipse region but it is not offsetting. I am new in visual studio C++;
I am using Visual studio 2019.
The problem is that, once output has been rendered to the given device context (as by your pDC->FillRgn(&rgnA, &brA) call), any subsequent changes to that region will not change the displayed output.
In order to do this, you will need to perform a cycle of operations:
Draw the object in its original position.
Then, when moved, erase that original object; and...
Draw the object in its new position.
Repeat each time you move the object.
You typically perform the 'erase' operation by drawing the object each time using the XOR (exclusive or) 'mix mode' on the target device context. Thus, the first time you draw this, it will paint the object over the background, and the second time, it will restore that background. You can use the CDC::SetROP2(R2_XORPEN) function to set that mode.
Here's a 'scruffy' (but runnable) program demonstrating the use of this XOR drawing technique. It uses 'raw' Windows GDI calls (rather than MFC) and is a Console-Mode program, but it will hopefully show the basic principles of the technique:
#include <Windows.h>
#define BLOBSIZE 20
#define MOVESTEP 5
int main()
{
SetProcessDPIAware(); // Comment this line out if using 'older' versions of Windows.
HWND hWnd = GetConsoleWindow();
HDC hDC = GetDC(hWnd);
RECT rc; GetClientRect(hWnd, &rc);
int x = 50, y = 30;
int dx = MOVESTEP, dy = MOVESTEP;
SetROP2(hDC, R2_XORPEN);
SelectObject(hDC, GetStockObject(WHITE_BRUSH));
SelectObject(hDC, GetStockObject(NULL_PEN));
do {
Ellipse(hDC, x, y, x + BLOBSIZE, y + BLOBSIZE); // First call: Draws the object
Sleep(10); // Here, we just wait, but you can do whatever else you want between moves
Ellipse(hDC, x, y, x + BLOBSIZE, y + BLOBSIZE); // Second call: erase it
x += dx;
if ((dx > 0 && x >= rc.right - BLOBSIZE) || (dx < 0 && x == 0)) {
Beep(1000, 10);
dx *= -1;
}
y += dy;
if ((dy > 0 && y >= rc.bottom - BLOBSIZE) || (dy < 0 && y == 0)) {
Beep(1000, 10);
dy *= -1;
}
} while (!(GetAsyncKeyState(VK_SPACE) & 0x8000)); // Press spacebar to quit!
ReleaseDC(hWnd, hDC);
return 0;
}

WinApi LineTo not refreshing the line

I'm just starting learning C++. All I want to do is drawing a line to a specified coord, which comes as an input in a method. I set the starting point in each loop (calling this function in a loop with different arguments) using MoveToEx and giving the coordinates in which I want lane to be drawn.
Any ideas how to make it working in a loop?
My code is similar to:
void Clock::drawSecondLine(float x,float y) {
HWND console_handle = GetConsoleWindow();
HDC device_context = GetDC(console_handle);
HPEN pen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
SelectObject(device_context, pen);
MoveToEx(device_context, 0, 0, NULL);
Ellipse(device_context, 400, 0, 0, 400);
MoveToEx(device_context, 200, 200, NULL);
LineTo(device_context, (int)x, (int)y);
ReleaseDC(console_handle, device_context);
cin.ignore();
}
And the loop:
void Zegar::startClock() {
while (true) {
drawSecondLine(laneShowingSecond.getX(), laneShowingSecond.getY());
laneShowingSecond.movePointByRadius(RADIUS_PER_SECOND);
Sleep(1000);
increaseSecond();
}
}
Here's some sample code (which I am running in VStudio 2k10).
Notes:
Although it is compiled as C++ (and uses some C++ features like iostream - meaning that it won't compile as C), it's still plain old C.
It has a lot of ugly stuff and NO-NO s (like global vars, lots defines, mixture of C and C++ code, drawing on the console window, ...). The goal was to have a PoC; the code can be cleansed later.
From the bounding rectangle (RECT_* defines (400, 0, 0, 400)) I am extracting the center coordinates, and the radii on the X and Y axes, using some simple mathematical calculations (due to the fact that the rectangle is a square, the 2 radii are equal so we hit the particular case where the ellipse is actually a circle).
nextPoint function is the replacement of laneShowingSecond.getX(), laneShowingSecond.getY() from your code.
Everything that needs to be only executed once (at the beginning) is placed in the init function. Note that if something goes wrong during initialization, it exits with an error (< 0) code, since it won't be able to draw.
Similarily, any cleanup stuff to be performed at the end is placed in the cleanup function (here I didn't bother to check for return codes since it's exiting anyway).
The draw function contains the drawing loop. In every iteration:
The angle is incremented by INCERMENT_DEG (which by default is 30°).
The next point on the ellipse is calculated using some simple trigonometrical formulas.
The drawing is performed.
A wait of ITERATION_SLEEP_TIME milliseconds (I've set it to 200 to avoid waiting one second for each line drawn) takes place.
Note that:
It stops after reaching 360° or 2 * PI Radians (round-the-clock), because it doesn't make sense to draw the same lines over and over again.
The drawing is performed counter-clockwise (positive angles in trigonometry); also the xOy origin (O(0, 0)) is screen's upper left corner.
Play with the defines that I marked with comments, by assigning various values to them and see how the drawing changes.
main.cpp:
#include <iostream>
#define _USE_MATH_DEFINES
#include <math.h>
#include <Windows.h>
#define RECT_LEFT 400 // Modify any of these 4 RECT_* values to get different ellipse shapes.
#define RECT_TOP 0
#define RECT_RIGHT 0
#define RECT_BOT 400
#define ITERATION_SLEEP_TIME 200 // Sleep time while in loop.
#define INCERMENT_DEG 30 // 30 degrees per step; a full circle has 360 (2 * PI RAD).
#define M_PI_180 M_PI / 180
using std::cout;
using std::endl;
typedef enum {DRAW_RADII, DRAW_POLY} DrawMethod;
const int radiusX = abs(RECT_RIGHT - RECT_LEFT) / 2;
const int radiusY = abs(RECT_BOT - RECT_TOP) / 2;
const int centerX = (RECT_RIGHT + RECT_LEFT) / 2;
const int centerY = (RECT_BOT + RECT_TOP) / 2;
HWND hwnd = NULL;
HDC hdc = NULL;
HPEN hpen = NULL;
DrawMethod meth = DRAW_RADII; // Modify this to DRAW_POLY to draw a polygon instead of the "bike wheel".
int deg = 0;
double x = 0, y = 0;
void nextPoint(int degree, double *x, double *y) {
*x = centerX + radiusX * cos(M_PI_180 * degree );
*y = centerY - radiusY * sin(M_PI_180 * degree);
}
int init() {
if ((hwnd = GetConsoleWindow()) == NULL) {
cout << "GetConsoleWindow error: " << GetLastError() << endl;
return -1;
}
if ((hdc = GetDC(hwnd)) == NULL) {
cout << "GetDC error: " << GetLastError() << endl;
return -2;
}
if ((hpen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0))) == NULL) {
cout << "CreatePen error: " << GetLastError() << endl;
return -3;
}
SelectObject(hdc, hpen);
Ellipse(hdc, RECT_LEFT, RECT_TOP, RECT_RIGHT, RECT_BOT);
nextPoint(deg, &x, &y);
if (meth == DRAW_RADII) {
MoveToEx(hdc, centerX, centerY, NULL);
LineTo(hdc, (int)x, (int)y);
} else if (meth == DRAW_POLY) {
MoveToEx(hdc, (int)x, (int)y, NULL);
}
return 0;
}
void draw() {
while (deg < 360) {
deg += INCERMENT_DEG;
nextPoint(deg, &x, &y);
if (meth == DRAW_RADII) {
MoveToEx(hdc, centerX, centerY, NULL);
LineTo(hdc, (int)x, (int)y);
} else if (meth == DRAW_POLY) {
LineTo(hdc, (int)x, (int)y);
} else
break;
Sleep(ITERATION_SLEEP_TIME);
}
}
void cleanup() {
if (hpen) {
DeleteObject(hpen);
}
if (hwnd && hdc) {
ReleaseDC(hwnd, hdc);
}
}
int main() {
if (!init())
draw();
cleanup();
return 0;
}

CScrollView only scrolls through parts of a large area

I encountered a problem scrolling large areas in a CScrollView. When slowly moving the scrollbar from top to bottom the behaviour is the following: At first the scrolling is working fine. At some point scrolling further does not do anything, instead the top of the area is shown. At some point scrolling starts again from the top.
Here is a small example. I created a new MFC project using the document-view-model and used a CScrollView as the view class. I added the following code to create a large area and add some text to show, which part is currently shown:
void CScrollViewTest2View::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal;
// TODO: calculate the total size of this view
sizeTotal.cx = sizeTotal.cy = 100*1000;
SetScrollSizes(MM_TEXT, sizeTotal);
for(int i = 0; i < 1000; i++)
{
CStatic* label = new CStatic();
label->Create(NULL, WS_CHILD | WS_VISIBLE, CRect(10,10 + i*100,100,30 + i*100), this);
CString text;
text.Format(L"%d",i);
label->SetWindowText(text);
}
}
If I add the following code I see that during the scrolling the 'nPos' value seems to wrap around. This would explain the behaviour. But I don't know how to work around that.
BOOL CScrollViewTest2View::OnScroll(UINT nScrollCode, UINT nPos, BOOL bDoScroll )
{
CString msg;
msg.Format(L"nPos = %u\n",nPos);
TRACE(msg);
return CScrollView::OnScroll(nScrollCode, nPos, bDoScroll);
}
and here is the output when it stops scrolling:
nPos = 27826
nPos = 29190
nPos = 30281
nPos = 31372
nPos = 31645
nPos = 32464
nPos = 4294938588
nPos = 4294939134
nPos = 4294939407
nPos = 4294939680
nPos = 4294940225
nPos = 4294940771
So is there a way to use a CScrollView to completely scroll down a large area?
The code of the sample project can be found here.
The static controls are not placed where you expect them to be. To see the problem, run the following code:
static CStatic test;
CRect r(0, 0, 100, 30);
r.MoveToY(40000);
test.Create(0, WS_CHILD | WS_VISIBLE, r, this);
test.GetWindowRect(r);
TRACE("%d\n", r.top);
r.top should be 32767. This is because of 16bit limits in Windows. All of the controls whose x/y position exceed this limit, are pushed back to this position.
OnScroll function suffers from a similar problem, however this can be fixed by using GetScrollInfo
Add ON_WM_VSCROLL to message map and the following OnVScroll override to your class
void CScrollViewTest2View::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
CView::OnVScroll(nSBCode, nPos, pScrollBar);
SCROLLINFO info;
GetScrollInfo(SB_VERT, &info, SIF_ALL);
int pos = info.nPos;
int save = pos;
switch (nSBCode)
{
case SB_LEFT: pos = info.nMin; break;
case SB_RIGHT: pos = info.nMax; break;
case SB_LINELEFT: pos--; break;
case SB_LINERIGHT: pos++; break;
case SB_PAGELEFT: pos -= info.nPage; break;
case SB_PAGERIGHT: pos += info.nPage; break;
case SB_THUMBPOSITION: pos = info.nTrackPos; break;
case SB_THUMBTRACK: pos = info.nTrackPos; break;
}
//make sure the new position is within range
if (pos < info.nMin) pos = info.nMin;
int max = info.nMax - info.nPage + 1;
if (pos > max) pos = max;
OnScrollBy(CSize(0, pos - save), 1);
//EDIT: moved this line after OnScrollBy and added condition
if (info.nPos != pos)
{
info.nPos = pos;
SetScrollInfo(SB_VERT, &info, FALSE);
}
UpdateWindow();
}
This will not solve the problem with windows controls whose x/y position is greater than 32,000, but at least it will scroll the DC as expected.
For testing, remove the static controls. You can use the draw function below to test the painting:
void CScrollViewTest2View::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal(0, 100 * 1000);
SetScrollSizes(MM_TEXT, sizeTotal);
}
void CScrollViewTest2View::OnDraw(CDC* pDC)
{
for (int i = 0; i < 1000; i++)
{
int y = i * 100;
CString s;
s.Format(L"%d ", y);
pDC->TextOutW(200, y, s);
}
}

Event handling bug with SDL

I am making a snake game using SDL and C++, and I found a bug involving events.
In this game, there are two important functions for now : the play function (the "main" function of the game) and the pause function. When the P key is pressed, the play function calls and executes the pause function. In it, you have two possibilities: either continue or quit the game.
Now, here is the problem. When I try to click "Continue game", the game flashes for a split second and comes back to the pause menu. When I keep the mouse button down, I realize the game itself is frozen until an event of any kind happens. If it does, it just comes back to where it was.
After some tests, I also noticed that the bug was due to the SDL_PollEvent function. So I changed this function in the play function to SDL_WaitEvent, which put the game back when I asked him to. However, another problem is created: since, in my play function, there is a timer that uses SDL_GetTicks, the game needs a trigger event to be fluid. For example, when I constantly move the cursor in any direction, the game can run. When I stop, it freezes.
Is there a way to fix this bug?
Here is the event handler in the play function:
SDL_PollEvent(&event); // Handle events
switch(event.type)
{
case SDL_QUIT:
quit = true;
break;
case SDL_KEYDOWN:
switch(event.key.keysym.sym) // Change direction at certain key press
{
case SDLK_s:
currentDirection = SNAKE_DOWN;
break;
case SDLK_w:
currentDirection = SNAKE_UP;
break;
case SDLK_a:
currentDirection = SNAKE_LEFT;
break;
case SDLK_d:
currentDirection = SNAKE_RIGHT;
break;
case SDLK_ESCAPE: // Quit if escape key pressed
quit = true;
break;
case SDLK_p:
if (pause(screen) == false)
quit = true;
break;
break;
default: ; // Code to block the compiler's -Wall flag (does not do anything to the program itself)
}
break;
}
And here is the complete pause function:
bool pause(SDL_Surface *screen)
{
SDL_Surface *pauseMessage, *continueButton, *quitButton;
SDL_Rect posPauseMessage, posContinueButton, posQuitButton;
TTF_Font *fipps;
SDL_Color color = {255, 255, 255};
SDL_Event event;
int x = 0, y = 0;
bool quit = false, returnFalse = false;
fipps = TTF_OpenFont("fipps.ttf", 50);
pauseMessage = TTF_RenderText_Blended(fipps, "PAUSED", color);
continueButton = TTF_RenderText_Blended(fipps, "Continue Game", color);
quitButton = TTF_RenderText_Blended(fipps, "Exit to menu", color);
posPauseMessage.x = (screen->w - pauseMessage->w) / 2;
posPauseMessage.y = 200;
posContinueButton.x = (screen->w - continueButton->w) / 2;
posContinueButton.y = posPauseMessage.y + 200;
posQuitButton.x = (screen->w - quitButton->w) / 2;
posQuitButton.y = posContinueButton.y + 100;
while (!quit)
{
SDL_WaitEvent(&event);
switch(event.type)
{
case SDL_QUIT:
returnFalse = true;
quit = true;
break;
case SDL_MOUSEBUTTONUP:
x = event.button.x;
y = event.button.y;
if ((x > posContinueButton.x) && (x < posContinueButton.x + continueButton->w) && (y > posContinueButton.y) && (y < posContinueButton.y + continueButton->h))
quit = true;
else if ((x > posQuitButton.x) && (x < posQuitButton.x + quitButton->w) && (y > posQuitButton.y) && (y < posQuitButton.y + quitButton->h))
returnFalse = true;
quit = true;
break;
}
SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0));
SDL_BlitSurface(pauseMessage, NULL, screen, &posPauseMessage);
SDL_BlitSurface(continueButton, NULL, screen, &posContinueButton);
SDL_BlitSurface(quitButton, NULL, screen, &posQuitButton);
SDL_Flip(screen);
}
TTF_CloseFont(fipps);
SDL_FreeSurface(pauseMessage);
SDL_FreeSurface(continueButton);
SDL_FreeSurface(quitButton);
if (returnFalse)
return false;
return true;
}
Thanks to #jordsti, I found a solution : I need to put the SDL_PollEvent as a condition for a while loop, which contains the switch block.

Win32 GDI Drawing a circle?

I am trying to draw a circle and I am currently using the Ellipse() function.
I have the starting mouse coordinates - x1 and y1 and the ending coordinates x2 and y2. As you can see, I am forcing the y2(temp_shape.bottom) to be = y1+(x2-x1). This doesn't work as intended. I know the calculation is completely wrong but any ideas on what is right?
Code Below.
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
RECT rect;
GetClientRect(hWnd, &rect);
HDC backbuffDC = CreateCompatibleDC(hdc);
HBITMAP backbuffer = CreateCompatibleBitmap( hdc, rect.right, rect.bottom);
int savedDC = SaveDC(backbuffDC);
SelectObject( backbuffDC, backbuffer );
HBRUSH hBrush = CreateSolidBrush(RGB(255,255,255));
FillRect(backbuffDC,&rect,hBrush);
DeleteObject(hBrush);
//Brush and Pen colours
SelectObject(backbuffDC, GetStockObject(DC_BRUSH));
SetDCBrushColor(backbuffDC, RGB(255,0,0));
SelectObject(backbuffDC, GetStockObject(DC_PEN));
SetDCPenColor(backbuffDC, RGB(0,0,0));
//Shape Coordinates
temp_shape.left=x1;
temp_shape.top=y1;
temp_shape.right=x2;
temp_shape.bottom=y2;
//Draw Old Shapes
//Rectangles
for ( int i = 0; i < current_rect_count; i++ )
{
Rectangle(backbuffDC, rect_list[i].left, rect_list[i].top, rect_list[i].right, rect_list[i].bottom);
}
//Ellipses
for ( int i = 0; i < current_ellipse_count; i++ )
{
Ellipse(backbuffDC, ellipse_list[i].left, ellipse_list[i].top, ellipse_list[i].right, ellipse_list[i].bottom);
}
if(mouse_down)
{
if(drawCircle)
{
temp_shape.right=y1+(x2-x1);
Ellipse(backbuffDC, temp_shape.left, temp_shape.top, temp_shape.right, temp_shape.bottom);
}
if(drawRect)
{
Rectangle(backbuffDC, temp_shape.left, temp_shape.top, temp_shape.right, temp_shape.bottom);
}
if(drawEllipse)
{
Ellipse(backbuffDC, temp_shape.left, temp_shape.top, temp_shape.right, temp_shape.bottom);
}
}
BitBlt(hdc,0,0,rect.right,rect.bottom,backbuffDC,0,0,SRCCOPY);
RestoreDC(backbuffDC,savedDC);
DeleteObject(backbuffer);
DeleteDC(backbuffDC);
EndPaint(hWnd, &ps);
}
break;
If you want Ellipse() to draw a perfectly round circle, you need to give it coordinates for a perfectly square shape, not a rectangular shape.
Assuming x1,y1 are the starting coordinates of the dragging and x2,y2 are the current mouse coordinates, then try this:
//Shape Coordinates
temp_shape.left = min(x1, x2);
temp_shape.top = min(y1, y2);
temp_shape.right = max(x1, x2);
temp_shape.bottom = max(y1, y2);
...
if (drawCircle)
{
int length = min(abs(x2-x1), abs(y2-y1));
if (x2 < x1)
temp_shape.left = temp_shape.right - length;
else
temp_shape.right = temp_shape.left + length;
if (y2 < y1)
temp_shape.top = temp_shape.bottom - length;
else
temp_shape.bottom = temp_shape.top + length;
Ellipse(backbuffDC, temp_shape.left, temp_shape.top, temp_shape.right, temp_shape.bottom);
}
I have worked out a calculation which works better. Pasted below for anyone else wanting the same.
if(drawSquare)
{
int xdiff = abs(x2-x1);
int ydiff=abs(y2-y1);
if(xdiff>ydiff)
{
if(y2>y1)
temp_shape.bottom=y1+xdiff;
else
temp_shape.bottom=y1-xdiff;
}
else
{
if(x2>x1)
temp_shape.right=x1+ydiff;
else
temp_shape.right=x1-ydiff;
}
Rectangle(backbuffDC, temp_shape.left, temp_shape.top, temp_shape.right, temp_shape.bottom);
}
Your code is unnecessarily complex with temp DCs and back buffers and you are recreating GDI brushes in every WM_PAIT? But that's besides the point. Here is the question : why do you do this:
temp_shape.right=y1+(x2-x1); //basing horizontal coordinate on vertical?
What framework stack is this on top of? .NET, then why don't you use built-in double buffering?