Need Help positioning my connect four board with CSS

Hey friends. I’m trying to make a connect four game in JS and it took me a while to position the board where I want it. I was eventually able to do it using flexbox. I then wanted to add a red chip that would hover over the board like this

I was able to position this using position: relative, which I now know isn’t really smart.
So this time around i want to position the chip using flexbox. The problem is when I place the div that represents the chip above the board it moves the board up and over to the right?

This is what my new version looked like before I started to make that chip

Then I added the div.chip to the html and added flex-direction: column; to the CSS in an attempt to straighten everything back up, which didn’t work so now I have this

https://imgur.com/a/FabWgme

Any help on this issue would be greatly appreciated, Thanks:)

https://jsfiddle.net/3cdu0tyo/?utm_source=website&utm_medium=embed&utm_campaign=3cdu0tyo

Have you ever completed a course in basic HTML and CSS?

The issues that you are having with your layout are very fundamental. Your code is written and formatted unusually well and you are not asking your layout to do anything unreasonable, therefore it seems that your knowledge of the fundamentals of HTML and CSS seem to be incomplete. What’s up?

No it doesn’t !

The board is already to the right. Remove .outer-chip and you will see the board remains to the right. Adding outer-chip does not affect anything in the code you have shown.

The board is aligned to the right because you have set it to display there by this rule in the body tag.

body {
	align-items: flex-end;
}

That has nothing to do with outer-chip at all.:slight_smile:

Just for fun I put up a codepen that does the hover effects without js.

I’m not saying this is the way to do it but just to give you other ideas. It alsoe should be made responsive and not rely on magic numbers but that would be possible with a little more work.

I’m not sure a table is semantic for a game unless you can ensure the relationship between the counters is made obvious in some ways. I feel that it may just be too hard to do where you have rows columns and diagonals that need to be in a relationship that assisted technologies could identify.

@PaulOB the align-items: flex-end; was so I could push the game board to the bottom of the webpage. removing it will stretch the board the full width of the screen? still leaving that gap(which looks to have the same height as that red bar) underneath the board. I need the bottom of the board touching the bottom of the page and it needs to be centered. I will then focus turning that red bar into a chip

I have completed that course, I might not have remembered eveything I’ve learned though. What specifically looks funky to you. And would happen to know a solution that would move the board to the center and bottom of the page? I’ll I know is position: relative/absolute; which I don’t wanna use and flexbox with justify content and align items which isn’t working?

I just scrapped everything and copied your code pen. I really don’t understand most of the css so if you could give me a detailed explanation of the code listed below I’d appreciate it.

1. *, *:before, *:after {
	box-sizing: inherit;
}

2. .connect > div:not(.counter):after {
	content:"";
	z-index:-1;
	position:absolute;
	left:0;
	right:0;
	top:-999em;
	bottom:-999em;
	background:transparent;
	transition: all 0.5s ease;
}

3. .connect > div.counter:after {
	content:"";
	display:block;
	padding:0;
	position:absolute;
	z-index:100;
	top:0px;
	left:0;
	right:auto;
	height:40px;
	width:40px;
	border-radius:50%;
	background:red;
	transform:translateX(0);
	transition:transform .5s ease,opacity .2s ease;
	opacity:0;
}

4. .connect > div:nth-of-type(7n+1):hover ~ .counter:after{transform:translateX(36px);opacity:1;}
.connect > div:nth-of-type(7n+2):hover ~ .counter:after{transform:translateX(136px);opacity:1;}
.connect > div:nth-of-type(7n+3):hover ~ .counter:after{transform:translateX(236px);opacity:1;}
.connect > div:nth-of-type(7n+4):hover ~ .counter:after{transform:translateX(336px);opacity:1;}
.connect > div:nth-of-type(7n+5):hover ~ .counter:after{transform:translateX(436px);opacity:1;}
.connect > div:nth-of-type(7n+6):hover ~ .counter:after{transform:translateX(536px);opacity:1;}
.connect > div:nth-of-type(7n+7):hover ~ .counter:after{transform:translateX(636px);opacity:1;}

No not on the demo you provided it won’t because its on the wrong axis. That’s why it pushes it to the right and not the bottom. Most times you can just use an auto margin on the side you want pushed to the edge of the container.

You didn’t really need to start again as it was basically working with the tweaks I mentioned :slight_smile:

i.e. You just needed this:

body {
	display: flex;
	justify-content: center;
	align-items: center;
	min-height: 100vh;
	background-color: #e6e6e6;
	flex-direction: column;
}

When you use flex-direction:column the axis is reversed and justify and align properties refer to the new axis.

I’ll try :slight_smile:

1. *, *:before, *:after {
	box-sizing: inherit;
}

That sets the border-box model so that padding margins and borders are kept inside the element and not added to its width (unlike the default model). It makes it easier to maintain when doing demos like this and indeed is the method I use for all designs these days.

Basically if you set a div to width:300px and add 2em padding and 5px borders the total size is still 300px.

More info here.

2. .connect > div:not(.counter):after {
	content:"";
	z-index:-1;
	position:absolute;
	left:0;
	right:0;
	top:-999em;
	bottom:-999em;
	background:transparent;
	transition: all 0.5s ease;
}

This uses the content property combined with the :after pseudo element. this allows us to create an element without using extra mark up. I could have used a div instead but why waste an element.

The :not property just excluded the properties in that rule being written into .counter as that is also a child div of .connect.

The above rule supplies the column hover colour by over-sizing the pseudo element so that it is much taller than the column (each div when hovered creates a box using the :after element that is taller than the whole column). The extra height is hidden with overflow:hidden on the parent to complete the effect.

3. .connect > div.counter:after {
	content:"";
	display:block;
	padding:0;
	position:absolute;
	z-index:100;
	top:0px;
	left:0;
	right:auto;
	height:40px;
	width:40px;
	border-radius:50%;
	background:red;
	transform:translateX(0);
	transition:transform .5s ease,opacity .2s ease;
	opacity:0;
}

Same method as the previous one except we create the counter with this one and position it at the top of the layout.

.connect > div:nth-of-type(7n+1):hover ~ .counter:after{transform:translateX(36px);opacity:1;}
.connect > div:nth-of-type(7n+2):hover ~ .counter:after{transform:translateX(136px);opacity:1;}
.connect > div:nth-of-type(7n+3):hover ~ .counter:after{transform:translateX(236px);opacity:1;}
.connect > div:nth-of-type(7n+4):hover ~ .counter:after{transform:translateX(336px);opacity:1;}
.connect > div:nth-of-type(7n+5):hover ~ .counter:after{transform:translateX(436px);opacity:1;}
.connect > div:nth-of-type(7n+6):hover ~ .counter:after{transform:translateX(536px);opacity:1;}
.connect > div:nth-of-type(7n+7):hover ~ .counter:after{transform:translateX(636px);opacity:1;}

This rule says that when you hover over any div then move the counter by xx px. .counter is a sibling so we can use the ~ rule to select .counter when it follows a previous div.

The nth-of-type allows the css to apply different measurements based on which div is being hovered. As we have 7 across then 7n+1 selects all the divs in the first column. 7n+2 selects all the divs in the second column and so on.

The nth-child generator here is a useful tool to play around with.

https://css-tricks.com/examples/nth-child-tester/

The layout itself is just a flexbox grid with no special features other than allowing the boxes to wrap as required.

2 Likes

Thank you!!!

Part 1

Hey @PaulOB So i took your connect four design and did a few things. 1. I changed .connect-outer to .board-wrapper, then changed .connect to .board. The next big thing I did was comment out certain properties in most of the rules that didn’t change the overall appearance or function of the board. These are my questions. Please take your time and look at the properties that are commented out and please give me an explanation that a novice could understand as to why you put them there and why they don’t need to be there for the game to function like your original design. I’ve spent hours looking at your CSS and really need a good explanation to how this stuff is working, thank you!:slight_smile:

part 2

this part is similar to part 1. I’m just asking some more detailed questions about the effect of certain properties. I might ask something you already answered in part 1. In that case just ignore the question.

a. At the top of my CSS I put a red border around everything. I do this to see my elements as I begin to start working with them. If you uncomment that code an apply the border to everything you can see it widens the board an extra column?

b. I also noticed if I remove the position:relative; on board > div every column is darkened when the cursor is hovered over?

c. .board > div:not(.counter):after changed to .board > div:after doesn’t do anything to .counter?

d. This really isn’t a question but you’ve probably seen div.counter: before. Which is the inner circle inside .counter:after. Just wanted to know if there was a better way to make this, especially the rules that move it along with .counter:after. I just copied what you did with .counter:after and did the same thing with .counter:before. I tried to connect the two to shorten up the code like this

.board > div:nth-of-type(7n+1):hover ~ .counter:after, .counter:before{transform:translateX(14px);opacity: 1;}

but that didn’t work

e. ok last question. I’m ready to drop the chip but I’m not sure how? In my original design I used an html element for the floating chip not a pseudo element. I know I could push .counter:after & .counter:before down with the top property, but then I’d have to generate a new chip. which I don’t know how to do cause there pseudo elements? And then How would I place the chip in that div or slot so the js knows where its at?(also is using the DOM for the game logic like that a good idea?) Please Paul take your time with these questions. I’m in no rush and would appreciate drawn out but simple answers. Thank you again! your such a big help:)

Where is the code I am looking at? I didn’t see a red border in your first example.

Note that adding a 1px border will increase the size of the elements by 2px which means that they may no longer fit in the space allocated unless you have used the border-box model for everything which probably answers your question.

e.g. this is the box model I usually use.

html {
	box-sizing: border-box;
}
*, *:before, *:after {
	box-sizing: inherit;
}

Rather than using a border for testing use outline instead as that does not impact on layout and won’t break things whatever box model you choose.

Position:relative creates a positioning context for absolutely placed children. If you remove the position:relative then the absolute element will be placed in relation to the nearest ancestor that has a position defined. (If none exist then your absolute element ends up being placed from the root element (effectively the body).)

Therefore removing a position:relative that is being used as a positioning context for its children will result in breaking the intended display because the absolute elements gets placed in a different context to where it should have been.

I can’t see your code but the code above is basically saying don’t do anything to .counter. So if you are saying it doesn’t do anything to .counter then it is doing its job :slight_smile:

When you have a comma separated list of selectors they have nothing in common with each other apart from the property values you apply.

Therefore you need each rule to be a complete rule.
e.g.

.board > div:nth-of-type(7n+1):hover ~ .counter:after, 
.board > div:nth-of-type(7n+1):hover ~ .counter:before {transform:translateX(14px);opacity: 1;}

It’s just a comma separated list that share the property values. There is nothing inferred by being comma separated.

You don’t need to drop the chip at the top because you will actually require 42 counters (one for each space on the board). Your logic will need to decide which column is being hovered over and which space is available. You could then animate the counter into place. Imagine keeping all 42 counters above the board but hidden. When you click to drop you can drop the counter that matches the available square.

Of course there are a million other ways to do this and you could indeed create a new counter each time one counter is dropped into place but you would need to program this accordingly.

I have actually modified my original demo to allow a counter to drop into place just for proof of concept.

It will also swap colours automatically and declare a winner.

My JS is basic so don’t copy any of my code as the JS forum is the best place to get logic and reliable code. Indeed the logic part is something that you need to work out for yourself so that you can build your code to achieving what you want to happen.

Hey @PaulOB I’m not gonna copy your code, I want to solve this with my own logic and reasoning. However I do value efficiency. The last connect four game I made had a lot of magic numbers(to move the chip using position: relative) and I had an array of every possible winning combo to filter through and check(It was a huge array & took forever to write out. And after looking at your checkWinner() function it made me realize I’m really behind the curve. I just ask, is it possible for you to add a comment to every line of JS describing exactly what’s happening step by step? So I can understand. If not that’s cool.

Hi,

As I say all the time JS is not my forte so you would get better logic asking in the JS forum as I’m sure my code could be halved and simplified :slight_smile:

However, I’ll explain what I did to get it working and remember this is my first version and was just about getting something to work. Optimisation and efficiency would be the next step (as well as making the whole design fluid).

Firstly I decide that I would use the before element to draw the ball in each square and that is done with CSS.

.filled:before{
	content:"";
	display:block;
	position:absolute;
	left:0;
	right:0;
	top:0;
	bottom:0;
	margin:auto;
	width:40px;
	height:40px;
	border-radius:50%;
	background:red;
	animation:fadeBall .5s ease  forwards;
	transform:translateY(-580px);
	z-index:101;
	box-shadow:10px 10px 10px rgba(0,0,0,0.7);
}
.filled.player1:before{background:red;}
.filled.player2:before{background:orange;}

@keyframes fadeBall{
	to{transform:translateY(0)}
}

The JS adds the class called .filled and soon as that is added the counter is animated from above the board and into its correct position. In order to simplify things I translated all the counters from -580px on the Y axis (basically upwards) so that they were hidden above the top of the board as I hidden the overflow. When the class is added the counter will animate back to a zero position which is in the square to which it is positioned from. The 580px is a bit of magic number but is basically taller than the height of the board. It doesn’t really matter they they all animate to different squares as the animation is the same duration for each.

In the JS I set up an array that listed the column numbers. (All cells had classes added to them when the JS starts to make it easier to keep track.)

columns = [
            ["c0", "c7", "c14", "c21", "c28", "c35"],
            ["c1", "c8", "c15", "c22", "c29", "c36"],
            ["c2", "c9", "c16", "c23", "c30", "c37"],
            ["c3", "c10", "c17", "c24", "c31", "c38"],
            ["c4", "c11", "c18", "c25", "c32", "c39"],
            ["c5", "c12", "c19", "c26", "c33", "c40"],
            ["c6", "c13", "c20", "c27", "c34", "c41"]
        ];

As you see each row of the array indicates the class name of a vertical connect 4 column. The JS detects which column was clicked and will then remove the last entry in the column.

columns[i].splice(columns[i].length - 1, 1);

That means that the array gets shorter and shorter for each column so we know what spaces are available. Eventually a column may get to zero so we know there is nothing to fill in that column.

(To be honest you could probably have worked this with one array as I use another array for the actual cell positions but I wanted to keep it simple and get it working first.)

The main board is held in another array:

 columnWin = [
            ["c0", "c6", "c12", "c18", "c24", "c30", "c36"],
            ["c1", "c7", "c13", "c19", "c25", "c31", "c37"],
            ["c2", "c8", "c14", "c20", "c26", "c32", "c38"],
            ["c3", "c9", "c15", "c21", "c27", "c33", "c39"],
            ["c4", "c10", "c16", "c22", "c28", "c34", "c40"],
            ["c5", "c11", "c17", "c23", "c29", "c35", "c41"]
        ]

The above array contains the classnames that make the connect four grid (remember our html is just a linear selection of divs.) When a counter is dropped I change the cell classname in the array to say the playername instead. In that way we can cycle through later and find 4 in sequence.

To select a winner you would cycle through that array in a loop and test if 4 items match.To make it easier I cycled horizontally, vertically and diagonally left and diagonally right in 4 separate loops. It could all be done in one loop I think but would look cumbersome.

When you move horizontally through the array you just need to loop through 4 items for each row because check the one in the loop and then the next + next + next which means you are checking 4 at a time. the logic for vertical and horizontal is much the same except that the next item in sequence will be a bit further away.

i.e. for a diagonal 4 going to the left you would need.

for (i = 0; i < 3; i++) {
                for (j = 3; j < 7; j++) {
                    if (columnWin[i][j] === theWinner &&
                        columnWin[(i + 1)][(j - 1)] === theWinner &&
                        columnWin[(i + 2)][(j - 2)] === theWinner &&
                        columnWin[(i + 3)][(j - 3)] === theWinner)
{

To check winners we just have to cycle through the ColumnWin array in blocks of 4 and to see if it has 4 player name matches in sequence so we can declare a winner.

Player names are toggled on each click and we change colours etc as required.

So to recap I check which column is being hovered and look up in the array for the classname at the bottom of each column. A class is added to that square in the html to trigger the animation and color the counter (i.e. the filled class and the playername class). Once the filled class is added the CSS does its work and animates the counter into its place. We then remove that entry from the column array so that we don’t fill it again.

To check winners we just have to cycle through the ColumnWin array in blocks of 4 and to see if it has 4 player name matches in sequence so we can declare a winner.

There is some complexity due to the fact that the html is linear while the connect four is a 7x 6 grid so we have to map between the two hence the need for the extra array.

In truth we could probably have done this without any arrays and just adding classes to the html and then checking the html on its own. I found it easier to visualise using the arrays and did this as a first iteration.

The next steps would have been to see how the whole thing could be simplified once a working version was created. It may be that a lot of the code is redundant but its so much easier once you have something working and then you can refine and make better.

Final steps would be to make the whole thing fluid in css and make it easier for touch users. Timings probably need to be altered as the JS acts quicker than the css animation so you would probably only declare winners after an animation has finished.

As I said you would be better asking in the JS forum for more succinct and logical approach once you decide how you want to programme this.:slight_smile:

I have also added some comments to the demo above so hope it helps anyway.

1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.