Live site: https://jeanwll.github.io/ConnectFour/
This is a solution to the Connect Four game challenge on Frontend Mentor. Frontend Mentor challenges help you improve your coding skills by building realistic projects.
Users should be able to:
- View the game rules
- Play a game of Connect Four against another human player (alternating turns on the same computer)
- View the optimal layout for the interface depending on their device's screen size
- See hover and focus states for all interactive elements on the page
- See the discs animate into their position when a move is made
- Play against the computer
- Control the game with keyboard (Arrow keys and SpaceBar)
- Object Oriented JavaScript
- CSS components workflow
- Flexbox
I experimented displaying game data with pseudo elements content
property, allowing simpler markup and CSS selectors controlling content format.
body[data-player='2'] .timer:before {
content: 'Player 2\'s turn';
}
body[data-pvc][data-player='2'] .timer:before {
content: 'CPU\'s turn';
}
.timer:after {
content: attr(data-value) 's';
}
<button class="btn-play | game-btn">Play</button>
body[data-state='end'] .btn-play:after {
content: ' Again';
}
The project included 3 distinct layouts, the grid had to keep an aspect-ratio and all the UI should fit in 100% of the height unlike usual webpages.
The game should perfectly fit any viewport above 360x580
.
@media (max-aspect-ratio: 840 / 580) and (max-width: 1079px) {
@media (max-width: 559px), (max-height: 859px) {
@media (max-width: 380px) and (max-height: 663px) {
}
@media (min-width: 381px), (min-height: 664px) {
}
}
@media (min-width: 560px) and (min-height: 860px) {
}
}
@media not all and (max-aspect-ratio: 840 / 580), (min-width: 1080px) {
}
Future media queries will make this much simpler:
@when (aspect-ratio <= 840 / 580) and (width <= 1079px) {
@when (width <= 559px), (height <= 859px) {
@when (width <= 380px) and (height <= 663px) {
}
@else {
}
}
@else {
}
}
@else {
}
Flexbox was very handy for resizing UI proportionally on different viewports.
flex-basis
is easier to implement from figma, more explicit and accurate than height%
.
.game__header {
flex-basis: 40px;
}
.scores {
flex-basis: 81px;
}
.grid {
flex-shrink: 0;
}
An other tool for supporting different viewports was clamp()
.
Let's say I wanted a font-size
to progressively reduce from 56px at viewport height = 880px
to 32px at viewport height = 580px
.
You can calculate the linear equation from 2 points (880, 56) and (580, 32) with the formula or an online calculator and get the result in vh + px
format.
font-size: clamp(32px, calc(20vh - 88px), 56px);
It was also a good opportunity to use min()
instead of height
and min-height
.
.game__wrapper {
height: min(807px, calc(100% - 20px));
aspect-ratio: 632 / 807;
}
I used the combination of margin-inline-start
and direction
to make my CSS less redundant.
.score {
margin-inline-start: 24px;
}
.score2 {
direction: rtl;
}
I used :is()
to make some selectors shorter.
:is(body:not([data-state='playing']), body[data-dropping]) .column--selected:before {
opacity: 0;
}
I used :where()
to impose a low specifity.
The same .info
element is used for text before game starts and after game ends.
body[data-state="start"] .info:before {
content: 'Player 1 starts';
}
body:where([data-pvc][data-player="2"]) .info:before {
content: 'CPU';
}
The current "CPU Opponent" only has a basic defensive strategy. Advanced algorithm for perfect play exists but would leave no chance for the player. Maybe we could have a submenu for CPU strength selection "Easy, Medium, Hard".
Having the game work with a shareable link to play Online PVP could be interesting! We could use the same submenu to select either "Local PVP" or "Online PVP".