0
\$\begingroup\$

So I've been at this for weeks now and every solution I programmed did not work. I am using Free Pascal and SDL2.

So I programmed a 2d game which uses a two-dimensional array which contains the tile numbers of tiles to be displayed. I also have another 2d array which contains the boolean values which determine whether the tile is walkable or not. Each tile is 8x8 pixels. At the start of the game I create a screen (sdl2 window) of 1920x1080 pixels. Although before drawing tiles I set the logical size to 688x387 = (86*8)x(48*8) roughly. SDL2 upscales this logical size to fit the window resolution.

I have camera grid coordinates (start x, start y, end x, end y) to determine the view area of the grid. To draw the area within the camera view I draw each tile within the camera coordinates on the screen, drawing the first tile (at camera start x, start y) at pixels 0,0 but then I offset that position by the camera offset coordinates x,y. I draw the next tile at pixel 8,0 then offset it by camera offset coordinates x,y and so on until all the tiles in camera grid coordinates start x, start y, end x, end y are drawn.

When I move the camera, I first change the offset coordinates (incrementing by 1 pixel or more and depending on the direction the player is moving in) and once those reach or exceed the tile size (8 pixels) I change the grid coordinates (I will upload a sample of my code).

I display the character on screen by checking its grid position relative to camera start x, start y. Then offset the character's position by the character offset coordinates x,y and also by the camera offset coordinates. I change the character grid and offset coordinates in the same way as I do the camera's.

This all gives me perfect pixel by pixel movement. However, when I try to do collision detection it does not work well. To check for collision I check if the character has just moved to new grid coordinates. Then I check the 2d grid which has the boolean values for the position where the character currently is. I check the next position if the character offset is greater than 0 and the previous position if the camera offset is greater than 0. It's actually a lot more detailed than that if you take a look at the code. I don't know if my varibales will mean much to you though but I'll upload the variable declarations too.

When I wasn't trying to do pixel by pixel movement and had a grid of 4x4 pixels tiles, and was moving the camera 4 pixels at a time, the collision was absolutely perfect because the character, camera and objects to collide with were all on the same grid and aligned to the same grid. But moving 4 pixels at a time does not look so smooth. But it is functional lol. I don't mind changing my code back to moving 4 pixels at a a time if you think this would be acceptable in a 2d action rpg game, which I would like as many people as possible to enjoy. I'm really tempted to go back to simple grid collision detection because I have tried every logical thing under the sun to get this collision detection working with pixel by pixel smooth movement and it just doesn't.

Maybe if you can explain in detail how exactly, for example, the SNES does collision detection considering that it can move tiles and character sprites pixel by pixel but yet checks for collision between tiles and sprites on a grid level. I have found nothing online which actually explains that part. All they mention is the fact that the SNES does that but they don't say how it's programmed or what the algorithm is.

No reply yet. Maybe my question is too specific. In essence I just need to figure out how to do collision detection between sprites and tiles given that each can move pixel by pixel and collision happens at a grid level (once detected at grid level I can detect at a pixel level between the sprite and tile).

var
    regiongrid:array[1..2280,1..2280,1..6] of word; { grid x, grid y, layers spr }
    regiongridm:array[1..2280,1..2280,1..6] of boolean; { grid x, grid y, layer walkability =true: non-walkable }
    camera:array[1..4] of smallint; { camera start x/y/end x/y }
    camera1:array[1..3] of shortint; { camera offset x/y/steps }
    camera2:array[1..4] of boolean; { =true: moving pixel by pixel to next grid position (1 down, 2 up, 3 right, 4 left }
    camera3:byte; { current zoom factor }
    camera4:byte; { default zoom factor }
    camera5:array[1..2] of shortint; { direction from 0,0 }
    cameram:array[1..2] of byte; { screen tile size x/y }
    cameram1:byte; { tiles size }
    partym:array[1..4] of byte; { total party char/controlled char/total dir/total tn }
    partym1:array[1..4,1..4,1..6] of word; { party, dir, tn spr }
    partym2:array[1..4,1..7] of byte; { party, total width/height/curr dir/curr tn/party number/previous dir/following distance }
    partym3:array[1..4,1..4] of uint32; { party, move speed start/end ticks/tile tn speed start/end ticks }
    partym4:array[1..4,1..2] of word; { party, stored coord x/y}
    partym5:array[1..4] of boolean; { party start moving }
    partym6:array[1..4] of boolean; { =true: party in dialogue }
    partym7:array[1..4] of boolean; { =true: move to target }
    partym8:array[1..2] of uint32; { increase move steps start ticks/delay ticks }
    partym9:array[1..4,1..2] of byte; { party, current steps/max steps }
    partym10:array[1..4,1..2] of shortint; { party, offset x/y }
    partym11:array[1..4,1..4] of boolean; { offset dir =true: went down from y=0/went up from y=0/went right from x=0/went left from x=0 }
    partym12:array[1..4,1..2] of shortint; { party, dir from 0,0 x/y }
    partyxy:array[1..4,1..2] of word; { party, region x/y }
    partyxy1:array[1..4,1..2] of byte; { party, world x/y }
    partyxy2:array[1..4,1..2] of word; { party, pixel x/y }
    joystick:psdl_joystick;
    joystickdeadzone,joystickwalkrun:integer;
    joystickopenstate:boolean; { =true: open }
    joystickuse:boolean;
    joystickbuttons:array[0..14] of boolean;
    joystickactive:boolean; { =true: active }
    joystickbuttonaction:array[0..14] of byte; { joystick button action num }
    joystickbuttonactionbool:array[0..14] of boolean; { joystick button action activate }
    joystickaxis:array[1..2,1..4] of boolean; { left/right joystick, axis direction (down/up/right/left) }
    joysticktriggers:array[1..2] of boolean; { left trigger/right trigger }
    joysticktriggersdeadzone:integer;
    joystickxy:array[1..4] of double; { left stick x/y axis/right stick x/y axis }
    joystickangle:smallint;
    joystick8dirtolangle:shortint;
    joystick8dirstate:array[1..2,1..8] of boolean; { left/right joystick, down/up/right/left/up+right/down+right/up+left/down+left }
    joystickmenuscrollticks, joystickmenuscrollticks1, joystickmenuscrollticks2, joystickmenuscrollticks3:array[1..4] of uint32; { scroll start ticks/delay/move delay start/delay ticks }
    joystickmenuscrollbool, joystickmenuscrollbool1, joystickmenuscrollbool6, joystickmenuscrollbool7:boolean; { =true: scrolled once }
    joystickmenuscrollbool2, joystickmenuscrollbool3, joystickmenuscrollbool4, joystickmenuscrollbool5:array[1..2] of boolean; { left stick move down/up, left/right, right stick move down/up, left/right }

procedure displayers; { using 8x8 tile map }
begin
    resolution[5]:=16*camera3;
    resolution[6]:=9*camera3;
    writeln(resolution[5],' ',resolution[6]);
    sdl_rendersetlogicalsize(sdlrenderer,resolution[5],resolution[6]);
    sdl_setrendertarget(sdlrenderer,nil);
    o[5]:=0;
    repeat
        o[5]:=o[5]+1;
        o[1]:=camera[1]-1; o[3]:=-cameram1;
        repeat
            o[1]:=o[1]+1;
            o[3]:=o[3]+cameram1;
            o[2]:=camera[2]-1; o[4]:=-cameram1;
            repeat
                o[2]:=o[2]+1;
                o[4]:=o[4]+cameram1;
                rect[1]^.x:=0; rect[1]^.y:=0; rect[1]^.w:=cameram1; rect[1]^.h:=cameram1;
                rect[2]^.x:=o[3]-camera1[1]; rect[2]^.y:=o[4]-camera1[2]; rect[2]^.w:=rect[1]^.w; rect[2]^.h:=rect[1]^.h;
                if (o[1]>=1) and (o[1]<=regionm[1]) and (o[2]>=1) and (o[2]<=regionm[2]) and (regiongrid[o[1],o[2],o[5]]>0) then  sdl_rendercopy(sdlrenderer,sprtextures[regiongrid[o[1],o[2],o[5]]],rect[1],rect[2]);
            until o[2]=camera[4]+2;
        until o[1]=camera[3]+1;
        if o[5]=3 then
        begin
            o[1]:=0;
            repeat
                o[1]:=o[1]+1;
                if (partyxy[o[1],1]>=camera[1]) and (partyxy[o[1],1]<=camera[3]) and (partyxy[o[1],2]>=camera[2]) and (partyxy[o[1],2]<=camera[4]) and (partym1[o[1],partym2[o[1],3],partym2[o[1],4]]>0) then
                begin
                    o[2]:=camera[1]-1; o[3]:=-cameram1;
                    repeat
                        o[2]:=o[2]+1;
                        o[3]:=o[3]+cameram1;
                    until o[2]=partyxy[o[1],1];
                    o[2]:=camera[2]-1; o[4]:=-cameram1;
                    repeat
                        o[2]:=o[2]+1;
                        o[4]:=o[4]+cameram1;
                    until o[2]=partyxy[o[1],2];
                    rect[1]^.x:=0; rect[1]^.y:=0; rect[1]^.w:=partym2[o[1],1]*cameram1; rect[1]^.h:=partym2[o[1],2]*cameram1;
                    { rect[2]^.x:=((partyxy[o[1],1]-camera[1]+1)*cameram1)+partym10[o[1],1]-camera1[1]; }
                    { rect[2]^.y:=((partyxy[o[1],2]-camera[2]+1)*cameram1)+partym10[o[1],2]-camera1[2]; }
                    rect[2]^.x:=o[3]+partym10[o[1],1]-camera1[1];
                    rect[2]^.y:=o[4]+partym10[o[1],2]-camera1[2];
                    rect[2]^.w:=rect[1]^.w; rect[2]^.h:=rect[1]^.h;
                    sdl_rendercopy(sdlrenderer,sprtextures1[partym1[o[1],partym2[o[1],3],partym2[o[1],4]]],rect[1],rect[2]);
                end;
            until o[1]=partym[1];
            { disp party char }
            o[1]:=0;
            repeat
                o[1]:=o[1]+1;
                if (npcanimm2[o[1],1]>=camera[1]) and (npcanimm2[o[1],1]+npcanimm9[o[1],npcanimm4[o[1],2],npcanimm4[o[1],3],1]<=camera[3]) and (npcanimm2[o[1],2]>=camera[2]) and (npcanimm2[o[1],2]+npcanimm9[o[1],npcanimm4[o[1],2],npcanimm4[o[1],3],2]<=camera[4]) then
                begin
                    o[2]:=camera[1]-1; o[3]:=-cameram1;
                    repeat
                        o[2]:=o[2]+1;
                        o[3]:=o[3]+cameram1;
                    until o[2]=npcanimm2[o[1],1];
                    o[2]:=camera[2]-1; o[4]:=-cameram1;
                    repeat
                        o[2]:=o[2]+1;
                        o[4]:=o[4]+cameram1;
                    until o[2]=npcanimm2[o[1],2];
                    rect[1]^.x:=0; rect[1]^.y:=0; rect[1]^.w:=npcanimm9[o[1],npcanimm4[o[1],2],npcanimm4[o[1],3],1]*cameram1; rect[1]^.h:=npcanimm9[o[1],npcanimm4[o[1],2],npcanimm4[o[1],3],2]*cameram1;
                    rect[2]^.x:=o[3]+npcanimm11[o[1],1]-camera1[1]; rect[2]^.y:=o[4]+npcanimm11[o[1],2]-camera1[2]; rect[2]^.w:=rect[1]^.w; rect[2]^.h:=rect[1]^.h;
                    sdl_rendercopy(sdlrenderer,sprtextures1[npcanimm8[o[1],npcanimm4[o[1],2],npcanimm4[o[1],3]]],rect[1],rect[2]);
                    o[6]:=0;
                    repeat
                        o[6]:=o[6]+1;
                        o[7]:=0;
                        repeat
                            o[7]:=o[7]+1;
                            if (npcanimm2[o[1],1]-1+o[6]>=1) and (npcanimm2[o[1],1]-1+o[6]<=regionm[1]) and (npcanimm2[o[1],2]-1+o[7]>=1) and (npcanimm2[o[1],2]-1+o[7]<=regionm[2]) then regiongridm[npcanimm2[o[1],1]-1+o[6],npcanimm2[o[1],2]-1+o[7],4]:=true;
                        until o[7]=npcanimm9[o[1],npcanimm4[o[1],2],npcanimm4[o[1],3],2];
                    until o[6]=npcanimm9[o[1],npcanimm4[o[1],2],npcanimm4[o[1],3],1];
                end;
            until o[1]=npcanimm;
            { disp npc }
        end;
    until o[5]=regiongridm1;
    if (tilecheckbox1=true) and (tilecheckbox[1]>=camera[1]) and (tilecheckbox[1]<=camera[3]) and (tilecheckbox[2]>=camera[2]) and (tilecheckbox[2]<=camera[4]) then
    begin
        o[1]:=camera[1]-1; o[2]:=-cameram1;
        repeat
            o[1]:=o[1]+1;
            o[2]:=o[2]+cameram1;
        until o[1]=tilecheckbox[1];
        o[1]:=camera[2]-1; o[3]:=-cameram1;
        repeat
            o[1]:=o[1]+1;
            o[3]:=o[3]+cameram1;
        until o[1]=tilecheckbox[2];
        rect[1]^.x:=0; rect[1]^.y:=0; rect[1]^.w:=cameram1; rect[1]^.h:=cameram1;
        rect[2]^.x:=o[2]; rect[2]^.y:=o[3]; rect[2]^.w:=cameram1; rect[2]^.h:=cameram1;
        sdl_rendercopy(sdlrenderer,sprtextures1[58],rect[1],rect[2]);
    end;
end;

procedure checkcollision1;
begin
    o[7]:=partyxy[partym[2],1];
    if partym10[partym[2],1]>0 then o[7]:=o[7]+1;
    { if camera1[1]>0 then o[7]:=o[7]-1; }
    o[8]:=partyxy[partym[2],2];
    if partym10[partym[2],2]>0 then o[8]:=o[8]+1;
    { if camera1[2]>0 then o[8]:=o[8]-1; }
    o[9]:=o[7]-1;
    repeat
        o[9]:=o[9]+1;
        o[10]:=o[8]-1;
        repeat
            o[10]:=o[10]+1;
            if regiongridm[o[9],o[10],4]=true then o[2]:=1;
        until (o[10]=o[8]+partym2[partym[2],2]-1) or (o[2]=1);
    until (o[9]=o[7]+partym2[partym[2],1]-1) or (o[2]=1);
end;

procedure runparty; { using 8x8 tiles }
begin
    if (joystickxy[1]>joystickwalkrun) or (joystickxy[2]>joystickwalkrun) or (joystickxy[1]<-joystickwalkrun) or (joystickxy[2]<-joystickwalkrun)  then
    begin
        partym9[partym[2],1]:=2;
        camera1[3]:=partym9[partym[2],1];
    end
    else
    begin
        partym9[partym[2],1]:=1;
        camera1[3]:=partym9[partym[2],1];
    end;
    if sdl_getticks-partym3[partym[2],1]>=partym3[partym[2],2] then
    begin
        partym3[partym[2],1]:=sdl_getticks;
        if joystickaxis[1,1]=true then
        begin
            o[2]:=0;
            o[3]:=partyxy[partym[2],1];
            o[4]:=partyxy[partym[2],2];
            o[5]:=partym10[partym[2],1];
            o[6]:=partym10[partym[2],2];
            if partym10[partym[2],2]<cameram1 then
            begin
                if partym10[partym[2],2]=0 then partym12[partym[2],2]:=1;
                partym10[partym[2],2]:=partym10[partym[2],2]+partym9[partym[2],1];
                updatepartysprturn;
                if partym10[partym[2],2]>=cameram1 then
                begin
                    if partyxy[partym[2],2]+partym2[partym[2],2]-1<regionm[2] then
                    begin
                        partym10[partym[2],2]:=0;
                        partym12[partym[2],2]:=0;
                        partyxy[partym[2],2]:=partyxy[partym[2],2]+1;
                        checkcollision1;
                        if o[2]=1 then
                        begin
                            partyxy[partym[2],1]:=o[3];
                            partyxy[partym[2],2]:=o[4];
                            partym10[partym[2],1]:=o[5];
                            partym10[partym[2],2]:=o[6];
                        end;
                    end
                    else
                    begin
                        partyxy[partym[2],1]:=o[3];
                        partyxy[partym[2],2]:=o[4];
                        partym10[partym[2],1]:=o[5];
                        partym10[partym[2],2]:=o[6];
                        o[2]:=1;
                    end;
                end;
            end;
            if (camera1[2]<cameram1) and (o[2]=0) and (partyxy[partym[2],2]>=round(((9*camera3)/cameram1)/2)) and (partyxy[partym[2],2]+partym2[partym[2],2]-1<=regionm[2]-round(((9*camera3)/cameram1)/2)) then
            begin
                if camera1[2]=0 then camera5[2]:=-1;
                camera1[2]:=camera1[2]+camera1[3];
                if camera1[2]>=cameram1 then
                begin
                    if camera[4]<regionm[2] then
                    begin
                        camera1[2]:=0;
                        camera5[2]:=0;
                        camera[2]:=camera[2]+1;
                        camera[4]:=camera[4]+1;
                    end
                    else
                    begin
                        camera1[2]:=camera1[2]-camera1[3];
                    end;
                end;
            end;
        end;
        if joystickaxis[1,2]=true then
        begin
            o[2]:=0;
            o[3]:=partyxy[partym[2],1];
            o[4]:=partyxy[partym[2],2];
            o[5]:=partym10[partym[2],1];
            o[6]:=partym10[partym[2],2];
            if partym10[partym[2],2]>-1 then
            begin
                if partym10[partym[2],2]=0 then partym12[partym[2],2]:=-1;
                partym10[partym[2],2]:=partym10[partym[2],2]-partym9[partym[2],1];
                updatepartysprturn;
                if partym10[partym[2],2]<=-1 then
                begin
                    if partyxy[partym[2],2]>1 then
                    begin
                        partym10[partym[2],2]:=cameram1-1;
                        partym12[partym[2],2]:=0;
                        partyxy[partym[2],2]:=partyxy[partym[2],2]-1;
                        checkcollision1;
                        if o[2]=1 then
                        begin
                            partyxy[partym[2],1]:=o[3];
                            partyxy[partym[2],2]:=o[4];
                            partym10[partym[2],1]:=o[5];
                            partym10[partym[2],2]:=o[6];
                        end;
                    end
                    else
                    begin
                        partyxy[partym[2],1]:=o[3];
                        partyxy[partym[2],2]:=o[4];
                        partym10[partym[2],1]:=o[5];
                        partym10[partym[2],2]:=o[6];
                        o[2]:=1;
                    end;
                end;
            end;
            if (camera1[2]>-1) and (o[2]=0) and (partyxy[partym[2],2]>=round(((9*camera3)/cameram1)/2)) and (partyxy[partym[2],2]+partym2[partym[2],2]-1<=regionm[2]-round(((9*camera3)/cameram1)/2)) then
            begin
                if camera1[2]=0 then camera5[2]:=1;
                camera1[2]:=camera1[2]-camera1[3];
                if camera1[2]<=-1 then
                begin
                    if camera[2]>1 then
                    begin
                        camera1[2]:=cameram1-1;
                        camera5[2]:=0;
                        camera[2]:=camera[2]-1;
                        camera[4]:=camera[4]-1;
                    end
                    else
                    begin
                        camera1[2]:=camera1[2]+camera1[3];
                    end;
                end;
            end;
        end;
        if joystickaxis[1,3]=true then
        begin
            o[2]:=0;
            o[3]:=partyxy[partym[2],1];
            o[4]:=partyxy[partym[2],2];
            o[5]:=partym10[partym[2],1];
            o[6]:=partym10[partym[2],2];
            if partym10[partym[2],1]<cameram1 then
            begin
                if partym10[partym[2],1]=0 then partym12[partym[2],1]:=1;
                partym10[partym[2],1]:=partym10[partym[2],1]+partym9[partym[2],1];
                updatepartysprturn;
                if partym10[partym[2],1]>=cameram1 then
                begin
                    if partyxy[partym[2],1]<regionm[1] then
                    begin
                        partym10[partym[2],1]:=0;
                        partym12[partym[2],1]:=0;
                        partyxy[partym[2],1]:=partyxy[partym[2],1]+1;
                        checkcollision1;
                        if o[2]=1 then
                        begin
                            partyxy[partym[2],1]:=o[3];
                            partyxy[partym[2],2]:=o[4];
                            partym10[partym[2],1]:=o[5];
                            partym10[partym[2],2]:=o[6];
                        end;
                    end
                    else
                    begin
                        partyxy[partym[2],1]:=o[3];
                        partyxy[partym[2],2]:=o[4];
                        partym10[partym[2],1]:=o[5];
                        partym10[partym[2],2]:=o[6];
                        o[2]:=1;
                    end;
                end;
            end;
            if (camera1[1]<cameram1) and (o[2]=0) and (partyxy[partym[2],1]>=round(((16*camera3)/cameram1)/2)) and (partyxy[partym[2],1]+partym2[partym[2],1]-1<=regionm[1]-round(((16*camera3)/cameram1)/2)) then
            begin
                if camera1[1]=0 then camera5[1]:=-1;
                camera1[1]:=camera1[1]+camera1[3];
                if camera1[1]>=cameram1 then
                begin
                    if camera[3]<regionm[1] then
                    begin
                        camera1[1]:=0;
                        camera5[1]:=0;
                        camera[1]:=camera[1]+1;
                        camera[3]:=camera[3]+1;
                    end
                    else
                    begin
                        camera1[1]:=camera1[1]-camera1[3];
                    end;
                end;
            end;
        end;
        if joystickaxis[1,4]=true then
        begin
            o[2]:=0;
            o[3]:=partyxy[partym[2],1];
            o[4]:=partyxy[partym[2],2];
            o[5]:=partym10[partym[2],1];
            o[6]:=partym10[partym[2],2];
            if partym10[partym[2],1]>-1 then
            begin
                if partym10[partym[2],1]=0 then partym12[partym[2],1]:=-1;
                partym10[partym[2],1]:=partym10[partym[2],1]-partym9[partym[2],1];
                updatepartysprturn;
                if partym10[partym[2],1]<=-1 then
                begin
                    if partyxy[partym[2],1]>1 then
                    begin
                        partym10[partym[2],1]:=cameram1-1;
                        partym12[partym[2],1]:=0;
                        partyxy[partym[2],1]:=partyxy[partym[2],1]-1;
                        checkcollision1;
                        if o[2]=1 then
                        begin
                            partyxy[partym[2],1]:=o[3];
                            partyxy[partym[2],2]:=o[4];
                            partym10[partym[2],1]:=o[5];
                            partym10[partym[2],2]:=o[6];
                        end;
                    end
                    else
                    begin
                        partyxy[partym[2],1]:=o[3];
                        partyxy[partym[2],2]:=o[4];
                        partym10[partym[2],1]:=o[5];
                        partym10[partym[2],2]:=o[6];
                        o[2]:=1;
                    end;
                end;
            end;
            if (camera1[1]>-1) and (o[2]=0) and (partyxy[partym[2],1]>=round(((16*camera3)/cameram1)/2)) and (partyxy[partym[2],1]+partym2[partym[2],1]-1<=regionm[1]-round(((16*camera3)/cameram1)/2)) then
            begin
                if camera1[1]=0 then camera5[1]:=1;
                camera1[1]:=camera1[1]-camera1[3];
                if camera1[1]<=-1 then
                begin
                    if camera[1]>1 then
                    begin
                        camera1[1]:=cameram1-1;
                        camera5[1]:=0;
                        camera[1]:=camera[1]-1;
                        camera[3]:=camera[3]-1;
                    end
                    else
                    begin
                        camera1[1]:=camera1[1]+camera1[3];
                    end;
                end;
            end;
        end;
    end;
end;
\$\endgroup\$
  • \$\begingroup\$ If any moderator is reading this, how do I upload my code? \$\endgroup\$ – Seb0029 May 4 at 12:02
  • 3
    \$\begingroup\$ You upload code by pasting a minimal complete verifiable example of the issue you want to isolate as text inside the body of the question. Use the "{}" button to ensure it's formatted correctly as code, or visit the help center to learn more about the markup available. \$\endgroup\$ – DMGregory May 4 at 12:03
  • \$\begingroup\$ You MUST learn to use proper formatting, or nobody will be able to read your code. Example: partym10[partym[2], 1] := partym10[partym[2], 1] - partym9[partym[2], 1]; \$\endgroup\$ – Almo May 5 at 17:33
  • \$\begingroup\$ Don't put lots of statements on one line... this is really hard to read. rect[1]^.x:=0; rect[1]^.y:=0; rect[1]^.w:=cameram1; rect[1]^.h:=cameram1; this should be on 4 different lines. \$\endgroup\$ – Almo May 5 at 17:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.