I am new to C++ and game programming. I am using SFML for graphics. I wrote a clone of Sokoban in my free time and to be honest, I am not happy about the result. I feel like I reused some code too many times. Please don't hesitate to criticize every bit of it. Also I am open to advices about the improvements and updates about the game.
#include "stdafx.h"
#include <SFML/Graphics.hpp> // For graphics
#include <iostream>
#include <fstream> // Necessary header for stream
#include <vector> // For 2D map
using namespace std;
vector<char> sokobanMap[20]; // The sokoban map
int level;
int row;
int col;
int totalMoves = 0;
int currentPosX;
int currentPosY;
bool CheckWinCondition() {
// If all boxes are placed, win
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (sokobanMap[i][j] == '@') {
return false;
}
}
}
return true;
}
void PushBox(int direction) {
// If box is unplaced, when the box is pushed the new spot is a free road
// If box is placed, new spot is a target.
// If the spot which the box will go to is T, it becomes a placed box, vice verca.
switch (direction)
{
case 0:{
if (sokobanMap[currentPosX - 1][currentPosY]=='@') {
sokobanMap[currentPosX - 1][currentPosY] = 'P';
if (sokobanMap[currentPosX - 2][currentPosY] == 'T') {
sokobanMap[currentPosX - 2][currentPosY] = 'X';
}
else {
sokobanMap[currentPosX - 2][currentPosY] = '@';
}
}
else {
sokobanMap[currentPosX - 1][currentPosY] = 'R';
if (sokobanMap[currentPosX - 2][currentPosY] == 'T') {
sokobanMap[currentPosX - 2][currentPosY] = 'X';
}
else {
sokobanMap[currentPosX - 2][currentPosY] = '@';
}
}
break;
}
case 1: {
if (sokobanMap[currentPosX + 1][currentPosY] == '@') {
sokobanMap[currentPosX + 1][currentPosY] = 'P';
if (sokobanMap[currentPosX + 2][currentPosY] == 'T') {
sokobanMap[currentPosX + 2][currentPosY] = 'X';
}
else {
sokobanMap[currentPosX + 2][currentPosY] = '@';
}
}
else {
sokobanMap[currentPosX + 1][currentPosY] = 'R';
if (sokobanMap[currentPosX + 2][currentPosY] == 'T') {
sokobanMap[currentPosX + 2][currentPosY] = 'X';
}
else {
sokobanMap[currentPosX + 2][currentPosY] = '@';
}
}
break;
}
case 2: {
if (sokobanMap[currentPosX][currentPosY + 1] == '@') {
sokobanMap[currentPosX][currentPosY + 1] = 'P';
if (sokobanMap[currentPosX][currentPosY + 2] == 'T') {
sokobanMap[currentPosX][currentPosY + 2] = 'X';
}
else {
sokobanMap[currentPosX][currentPosY + 2] = '@';
}
}
else {
sokobanMap[currentPosX][currentPosY + 1] = 'R';
if (sokobanMap[currentPosX][currentPosY + 2] == 'T') {
sokobanMap[currentPosX][currentPosY + 2] = 'X';
}
else {
sokobanMap[currentPosX][currentPosY + 2] = '@';
}
}
break;
}
case 3: {
if (sokobanMap[currentPosX][currentPosY - 1] == '@') {
sokobanMap[currentPosX][currentPosY - 1] = 'P';
if (sokobanMap[currentPosX][currentPosY - 2] == 'T') {
sokobanMap[currentPosX][currentPosY - 2] = 'X';
}
else {
sokobanMap[currentPosX][currentPosY - 2] = '@';
}
}
else {
sokobanMap[currentPosX][currentPosY - 1] = 'R';
if (sokobanMap[currentPosX][currentPosY - 2] == 'T') {
sokobanMap[currentPosX][currentPosY - 2] = 'X';
}
else {
sokobanMap[currentPosX][currentPosY - 2] = '@';
}
}
break;
}
default:
break;
}
}
bool CheckIfMoveIsLegal(int direction) {
//Checks all illegal posibilities. If non of them is true, return true.
switch (direction)
{
case 0: {
if (sokobanMap[currentPosX - 1][currentPosY] == '#') {
return false;
}
else if (sokobanMap[currentPosX - 1][currentPosY] == '@' && sokobanMap[currentPosX - 2][currentPosY] == '@') {
return false;
}
else if (sokobanMap[currentPosX - 1][currentPosY] == '@' && sokobanMap[currentPosX - 2][currentPosY] == '#') {
return false;
}
else if (sokobanMap[currentPosX - 1][currentPosY] == 'X' && sokobanMap[currentPosX - 2][currentPosY] == 'X') {
return false;
}
else if (sokobanMap[currentPosX - 1][currentPosY] == 'X' && sokobanMap[currentPosX - 2][currentPosY] == '#') {
return false;
}
else if (sokobanMap[currentPosX - 1][currentPosY] == 'X' && sokobanMap[currentPosX - 2][currentPosY] == '@') {
return false;
}
else if (sokobanMap[currentPosX - 1][currentPosY] == '@' && sokobanMap[currentPosX - 2][currentPosY] == 'X') {
return false;
}
else {
return true;
}
break;
}
case 1: {
if (sokobanMap[currentPosX + 1][currentPosY] == '#') {
return false;
}
else if (sokobanMap[currentPosX + 1][currentPosY] == '@' && sokobanMap[currentPosX + 2][currentPosY] == '@') {
return false;
}
else if (sokobanMap[currentPosX + 1][currentPosY] == '@' && sokobanMap[currentPosX + 2][currentPosY] == '#') {
return false;
}
else if (sokobanMap[currentPosX + 1][currentPosY] == 'X' && sokobanMap[currentPosX + 2][currentPosY] == 'X') {
return false;
}
else if (sokobanMap[currentPosX + 1][currentPosY] == 'X' && sokobanMap[currentPosX + 2][currentPosY] == '#') {
return false;
}
else if (sokobanMap[currentPosX + 1][currentPosY] == 'X' && sokobanMap[currentPosX + 2][currentPosY] == '@') {
return false;
}
else if (sokobanMap[currentPosX + 1][currentPosY] == '@' && sokobanMap[currentPosX + 2][currentPosY] == 'X') {
return false;
}
else {
return true;
}
break;
}
case 2: {
if (sokobanMap[currentPosX][currentPosY + 1] == '#') {
return false;
}
else if (sokobanMap[currentPosX][currentPosY + 1] == '@' && sokobanMap[currentPosX][currentPosY + 2] == '@') {
return false;
}
else if (sokobanMap[currentPosX][currentPosY + 1] == '@' && sokobanMap[currentPosX][currentPosY + 2] == '#') {
return false;
}
else if (sokobanMap[currentPosX][currentPosY + 1] == 'X' && sokobanMap[currentPosX][currentPosY + 2] == 'X') {
return false;
}
else if (sokobanMap[currentPosX][currentPosY + 1] == 'X' && sokobanMap[currentPosX][currentPosY + 2] == '#') {
return false;
}
else if (sokobanMap[currentPosX][currentPosY + 1] == 'X' && sokobanMap[currentPosX][currentPosY + 2] == '@') {
return false;
}
else if (sokobanMap[currentPosX][currentPosY + 1] == '@' && sokobanMap[currentPosX][currentPosY + 2] == 'X') {
return false;
}
else {
return true;
}
break;
}
case 3: {
if (sokobanMap[currentPosX][currentPosY - 1] == '#') {
return false;
}
else if (sokobanMap[currentPosX][currentPosY - 1] == '@' && sokobanMap[currentPosX][currentPosY - 2] == '@') {
return false;
}
else if (sokobanMap[currentPosX][currentPosY - 1] == '@' && sokobanMap[currentPosX][currentPosY - 2] == '#') {
return false;
}
else if (sokobanMap[currentPosX][currentPosY - 1] == 'X' && sokobanMap[currentPosX][currentPosY - 2] == 'X') {
return false;
}
else if (sokobanMap[currentPosX][currentPosY - 1] == 'X' && sokobanMap[currentPosX][currentPosY - 2] == '#') {
return false;
}
else if (sokobanMap[currentPosX][currentPosY - 1] == 'X' && sokobanMap[currentPosX][currentPosY - 2] == '@') {
return false;
}
else if (sokobanMap[currentPosX][currentPosY - 1] == '@' && sokobanMap[currentPosX][currentPosY - 2] == 'X') {
return false;
}
else {
return true;
}
break;
}
default:
break;
}
}
void MovePlayer(int direction) {
// Check if the move is legal
// Update the counter
// The move is legal if:
// 1- There is a free spot in the same direction of the input.
// 2- There is only one box in the same direction of the input.
if (CheckIfMoveIsLegal(direction)) {
totalMoves++;
// If player is standing on a target, when he leaves the spot becomes Target; if not, free spot
if (sokobanMap[currentPosX][currentPosY] == 'R') {
sokobanMap[currentPosX][currentPosY] = 'T';
}
else {
sokobanMap[currentPosX][currentPosY] = '.';
}
switch (direction) {
case 0: {
if (sokobanMap[currentPosX - 1][currentPosY] == '@' || sokobanMap[currentPosX - 1][currentPosY] == 'X') {
PushBox(direction);
}
else if (sokobanMap[currentPosX - 1][currentPosY] == 'T') {
sokobanMap[currentPosX - 1][currentPosY] = 'R';
}
else {
sokobanMap[currentPosX - 1][currentPosY] = 'P';
}
currentPosX = currentPosX - 1;
break;
}
case 1: {
if (sokobanMap[currentPosX + 1][currentPosY] == '@' || sokobanMap[currentPosX + 1][currentPosY] == 'X') {
PushBox(direction);
}
else if (sokobanMap[currentPosX + 1][currentPosY] == 'T') {
sokobanMap[currentPosX + 1][currentPosY] = 'R';
}
else {
sokobanMap[currentPosX + 1][currentPosY] = 'P';
}
currentPosX = currentPosX + 1;
break;
}
case 2: {
if (sokobanMap[currentPosX][currentPosY+1] == '@' || sokobanMap[currentPosX][currentPosY+1] == 'X') {
PushBox(direction);
}
else if (sokobanMap[currentPosX][currentPosY+1] == 'T') {
sokobanMap[currentPosX][currentPosY+1] = 'R';
}
else {
sokobanMap[currentPosX][currentPosY+1] = 'P';
}
currentPosY = currentPosY + 1;
break;
}
case 3: {
if (sokobanMap[currentPosX][currentPosY - 1] == '@' || sokobanMap[currentPosX][currentPosY - 1] == 'X') {
PushBox(direction);
}
else if (sokobanMap[currentPosX][currentPosY - 1] == 'T') {
sokobanMap[currentPosX][currentPosY - 1] = 'R';
}
else {
sokobanMap[currentPosX][currentPosY - 1] = 'P';
}
currentPosY = currentPosY - 1;
break;
}
}
}
}
int main()
{
ifstream inputFile; // Input file stream declaration
inputFile.open("C:\\Users\\AliTeo\\Desktop\\Sokoban_levels\\Sokoban_lvl_01.txt"); // Opening the text-map
if (!inputFile) {
cerr << "Unable to open file Sokoban_lvl_01.txt\n"; // Handling errors
exit(1);
}
/*
First three integers:
1-Level Number
2-Row Number
3-Column Number
Row X Column character c->
Meaning of the characters in the text map:
# : Wall
. : Free spot
@ : Box
X : Placed Box
P : Player Position
R : Player On a Target
T : Target
*/
inputFile >> level >> row >> col;
char ch;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
inputFile >> ch;
sokobanMap[i].push_back(ch);
}
}
sf::RenderWindow window(sf::VideoMode(col*32, row*32), "Sokoban");
sf::Texture wall;
sf::Texture road; // ::TO DO::
sf::Texture box;
sf::Texture placedBox;
sf::Texture player;
sf::Texture target;
if (!wall.loadFromFile("C:\\Users\\AliTeo\\Desktop\\Sokoban images\\wall.png")) {
// Handle an error.
}
if (!box.loadFromFile("C:\\Users\\AliTeo\\Desktop\\Sokoban images\\box.png")) {
// Handle an error.
}
if (!placedBox.loadFromFile("C:\\Users\\AliTeo\\Desktop\\Sokoban images\\placed_box.png")) {
// Handle an error.
}
if (!player.loadFromFile("C:\\Users\\AliTeo\\Desktop\\Sokoban images\\player.png")) {
// Handle an error.
}
if (!target.loadFromFile("C:\\Users\\AliTeo\\Desktop\\Sokoban images\\target.png")) {
// Handle an error.
}
if (!road.loadFromFile("filename.png")) {
// Handle an error.
}
// For printing on the console, for checking purposes.
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
cout << sokobanMap[i][j] << " ";
}
cout << endl;
}
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close(); // Close window button clicked.
}
else if (event.type == sf::Event::KeyPressed) {
int direction;
if (event.key.code == sf::Keyboard::Up) {
direction = 0;
MovePlayer(direction);
// Move Up
}
else if (event.key.code == sf::Keyboard::Down) {
direction = 1;
MovePlayer(direction);
// Move Down
}
else if (event.key.code == sf::Keyboard::Right) {
direction = 2;
MovePlayer(direction);
// Move Left
}
else if(event.key.code == sf::Keyboard::Left) {
direction = 3;
MovePlayer(direction);
//Move Right
}
}
}
window.clear(sf::Color::White);
// Drawing of the map
int posX = 0;
int posY = 0;
sf::Sprite obj;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (sokobanMap[i][j] == '#') {
// Draw Wall
//obj.setFillColor(sf::Color::Red);
obj.setTexture(wall);
obj.setPosition(posX,posY);
window.draw(obj);
}
else if (sokobanMap[i][j] == '.') {
// Draw Road ::TO DO:: maybe a sprite in the future
}
else if (sokobanMap[i][j] == '@') {
// Draw Box
obj.setTexture(box);
obj.setPosition(posX, posY);
window.draw(obj);
}
else if (sokobanMap[i][j] == 'P') {
// Draw The Player
currentPosX = i;
currentPosY = j;
obj.setTexture(player);
obj.setPosition(posX, posY);
window.draw(obj);
}
else if (sokobanMap[i][j] == 'R') {
// Draw The Player On The Target
currentPosX = i;
currentPosY = j;
obj.setTexture(player);
obj.setPosition(posX, posY);
window.draw(obj);
}
else if (sokobanMap[i][j] == 'T') {
// Draw Target
obj.setTexture(target);
obj.setPosition(posX, posY);
window.draw(obj);
}
else if (sokobanMap[i][j] == 'X') {
obj.setTexture(placedBox);
obj.setPosition(posX, posY);
window.draw(obj);
}
posX += 32;
}
// Next Row
posY += 32;
posX = 0;
}
//cout << currentPosX << " " << currentPosY << endl;
window.display();
}
inputFile.close();
return 0;
}