Sokoban Game
Course Design Purpose
The process and practice of developing high-quality learning settings and experiences for students
is known as course design. Students can access knowledge, acquire skills, and practise higher
levels of thinking through purposeful and planned exposure to instructional materials, learning
activities, and interaction. The goal of course design is to create the best learning experiences for
students in an atmosphere that encourages and values learning and intellectual development.
The context for good course design is that the courses themselves serve as the basis for teaching
and learning. More students will be able to participate in richer learning experiences that support
successful learning if the design is effective. The science of good course design is respected at
university, and all course components are purposeful. Courses, whether in general education or
program-specific education, must serve as the basis for student learning. As a consequence, good
course design should result in our programs having a positive impact and producing the desired
student results.
Course Design Idea
Sokoban is a puzzle video game in which the player pushes boxes around in a warehouse, trying
to get them to storage locations. The game was designed in 1981 by Hiroyuki Imabayashi, and
first published in December 1982.
the game is played on a board of squares, where each square is a floor or a wall. Some floor
squares contain boxes, and some floor squares are marked as storage locations.
The player is confined to the board and may move horizontally or vertically onto empty squares
(never through walls or boxes). The player can move a box by walking up to it and pushing it to
the square beyond. Boxes cannot be pulled, and they cannot be pushed to squares with walls or
other boxes. The number of boxes equals the number of storage locations. The puzzle is solved
when all boxes are placed at storage locations.
Implementation
Game.h
#include <stdio.h>
// default console screen size
#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 25
#define SCREEN_SIZE SCREEN_WIDTH * SCREEN_HEIGHT
// screen buffer that replaces stdout
char screen[SCREEN_SIZE];
// init windows headers
#ifdef WIN32
#include <windows.h>
#include <stdlib.h>
HANDLE console;
CONSOLE_CURSOR_INFO cursor;
COORD coord;
DWORD chars_to_write = 0;
#endif
// init unix headers
#ifdef unix
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#endif
void InitScreen()
{
// print directly to screen buffer
#ifdef WIN32
console = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL,
CONSOLE_TEXTMODE_BUFFER, NULL);
cursor.dwSize = 100;
cursor.bVisible = FALSE;
coord.X = 0;
coord.Y = 0;
SetConsoleActiveScreenBuffer(console);
SetConsoleCursorInfo(console, &cursor);
#endif
// use ANSI VT100 escape sequences hide cursor and clear screen
#ifdef unix
printf("\x1b[?25l");
printf("\x1b[2J");
#endif
}
void RefreshScreen()
{
// might not be needed on windows natively, needed on wine though
for(int scr_cell = 0; scr_cell < SCREEN_SIZE; scr_cell++)
if(!screen[scr_cell]) screen[scr_cell] = ' ';
// update screen buffer
#ifdef WIN32
screen[SCREEN_SIZE - 1] = 0;
WriteConsoleOutputCharacter(console, screen, SCREEN_WIDTH *
SCREEN_HEIGHT, coord, &chars_to_write);
#endif
// print screen buffer to stdout at coordinates 0, 0
#ifdef unix
printf("\x1b[0;0H%s", screen);
#endif
}
void PrintMap(int pos_x, int pos_y, int map_width, int map_height, char *map)
{
for (int row = 0; row < map_height; row++)
{
for (int col = 0; col < map_width; col++)
{
screen[(row + pos_y) * SCREEN_WIDTH + col + pos_x] = map[row * map_width +
col];
}
}
RefreshScreen();
}
// getchar() for windows without echoing
#ifdef WIN32
int getch()
{
DWORD mode, chars_to_read;
HANDLE console = GetStdHandle(STD_INPUT_HANDLE);
GetConsoleMode(console, &mode);
SetConsoleMode(console, mode & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT));
int key = 0;
ReadConsole(console, &key, 1, &chars_to_read, NULL);
SetConsoleMode(console, mode);
return key;
}
#endif
// getchar() for unix without echoing
#ifdef unix
int getch()
{
struct termios oldattr, newattr;
tcgetattr( STDIN_FILENO, &oldattr );
newattr = oldattr;
newattr.c_lflag &= ~( ICANON | ECHO );
tcsetattr( STDIN_FILENO, TCSANOW, &newattr );
int key = getchar();
tcsetattr( STDIN_FILENO, TCSANOW, &oldattr );
return key;
}
#endif
void Leave()
{
#ifdef WIN32
cursor.bVisible = TRUE;
SetConsoleCursorInfo(console, &cursor);
#endif
// show cursor escape sequence
#ifdef unix
printf("\x1b[?25h");
#endif
}
Sokoban-game.c
#include "game.h"
#define MAP_WIDTH 8
#define MAP_HEIGHT 10
#define PLAYER_POSITION pos_y * MAP_WIDTH + pos_x
char map[] = {
"##### "
"#xB ### "
"### # "
"#x@B # "
"### Bx# "
"#x##B # "
"# # x ##"
"#B OBBx#"
"# x #"
"########"
};
int dest_squares[10]; // array to store cell indexes for 'x' cells
int GetDestSquares() // init 'x' cells indexes
{
int count = 0, cell; // 'x' cell number, current cell index
for(int row = 0; row < MAP_HEIGHT; row++) // loop ower map rows
{
for(int col = 0; col < MAP_WIDTH; col++) // loop ower map columns
{
cell = row * MAP_WIDTH + col; // init current cell index
if(map[cell] == 'x' || map[cell] == 'O') // if 'x' cell is emty or with box on it
dest_squares[count++] = cell; // store it in array
}
}
return count; // return number of 'x' cells
}
void GetPosition(int *pos_x, int *pos_y)
{
int cell; // current cell index
for(int row = 0; row < MAP_HEIGHT; row++) // loop ower map rows
{
for(int col = 0; col < MAP_WIDTH; col++) // loop ower map columns
{
cell = row * MAP_WIDTH + col; // init current cell index
if(map[cell] == '@') // if current cell on the map contains player
{
*pos_x = col; // store player's x coordinate
*pos_y = row; // store player's y coordinate
}
}
}
}
void MoveCharacter(int pos_x, int pos_y, int offset)
{
if(map[PLAYER_POSITION + offset] != '#') // if player doesn't hit the wall
{
if(((map[PLAYER_POSITION + offset] == 'B') || // if player hits the box
(map[PLAYER_POSITION + offset] == 'O')) && // or the box on 'x' cell
(map[PLAYER_POSITION + offset * 2] != '#' || // and box doesn't hit a wall
map[PLAYER_POSITION + offset * 2] != 'B' || // or another box
map[PLAYER_POSITION + offset * 2] != 'O')) // or box on 'x' cell
{
map[PLAYER_POSITION] = ' '; // clear previous player's position
pos_x += offset; // update player's coordinate
if(map[PLAYER_POSITION + offset] == ' ') // if the square next to the box
is empty
map[PLAYER_POSITION + offset] = 'B'; // push the box
else if(map[PLAYER_POSITION + offset] == 'x') // if the square next to the
box is 'x'
map[PLAYER_POSITION + offset] = 'O'; // mark the box is on it's place
else
{
map[PLAYER_POSITION - offset] = '@'; // if box hits the wall or
another box
return; // don't push it any further
}
map[PLAYER_POSITION] = '@'; // draw the player in the new
position
}
else // if the square next to the player is empty
{
map[PLAYER_POSITION] = ' '; // clear previous player position
pos_x += offset; // update player's coordinate
map[PLAYER_POSITION] = '@'; // draw the player in the new
position
}
}
}
Functions of Main Modules in the System
int main()
{
InitScreen();
int key; // user input key
int pos_x, pos_y; // player's coordinates
int dest_count; // 'x' cells counter
int dest_num = GetDestSquares();
int center_x = SCREEN_WIDTH / 2 - MAP_WIDTH / 2;
int center_y = SCREEN_HEIGHT / 2 - MAP_HEIGHT / 2;
PrintMap(center_x, center_y, MAP_WIDTH, MAP_HEIGHT, map);
while(1)
{
if(key == 27) break;
key = getch();
GetPosition(&pos_x, &pos_y);
switch(key)
{
case 'w': MoveCharacter(pos_x, pos_y, - MAP_WIDTH); break;
case 's': MoveCharacter(pos_x, pos_y, MAP_WIDTH); break;
case 'a': MoveCharacter(pos_x, pos_y, - 1); break;
case 'd': MoveCharacter(pos_x, pos_y, 1); break;
}
dest_count = 0; // reset 'x' cells counter
for(int i = 0; i < 10; i++) // for all destination squares
{
if(map[dest_squares[i]] == 'O') dest_count++; // increase 'x' cells counter if box
is on 'x' cell
if(map[dest_squares[i]] == ' ') // if 'x' cell has been erased
map[dest_squares[i]] = 'x'; // restore it
}
PrintMap(center_x, center_y, MAP_WIDTH, MAP_HEIGHT, map);
// if all boxes are on it's places break out of game loop
if(dest_num == dest_count)
{
sprintf(screen + (SCREEN_WIDTH * SCREEN_HEIGHT / 2) - MAP_WIDTH / 2,
"YOU WIN!");
RefreshScreen();
break;
}
}
Leave();
return 0;
}
Sokoban.c
#include <stdio.h>
#include <string.h>
#define MAP_WIDTH 8
#define MAP_HEIGHT 9
#define PLAYER_POSITION pos_y * MAP_WIDTH + pos_x
char map[] = {
" ##### \n"
"### # \n"
"#x@B # \n"
"### Bx# \n"
"#x##B # \n"
"# # x ##\n"
"#B OBBx#\n"
"# x #\n"
"########\n"
};
/*#define MAP_WIDTH 14
#define MAP_HEIGHT 10
#define PLAYER_POSITION pos_y * MAP_WIDTH + pos_x
char map[] = {
"##############\n"
"# # xB #\n"
"# x # xB #\n"
"# B # xB #\n"
"# #### #\n"
"# @ # #\n"
"# # #\n"
"# B # #\n"
"# x #\n"
"##############\n"
};*/
int dest_squares[10]; // array to store cell indexes for 'x' cells
int GetDestSquares() // init 'x' cells indexes
{
int count, cell; // 'x' cell number, current cell index
for(int row = 0; row < MAP_HEIGHT; row++) // loop ower map rows
{
for(int col = 0; col < MAP_WIDTH; col++) // loop ower map columns
{
cell = row * MAP_WIDTH + col; // init current cell index
if(map[cell] == 'x' || map[cell] == 'O') // if 'x' cell is emty or with box on it
dest_squares[count++] = cell; // store it in array
}
}
return count - 1; // return number of 'x' cells
}
void GetPosition(int *pos_x, int *pos_y)
{
int cell; // current cell index
for(int row = 0; row < MAP_HEIGHT; row++) // loop ower map rows
{
for(int col = 0; col < MAP_WIDTH; col++) // loop ower map columns
{
cell = row * MAP_WIDTH + col; // init current cell index
if(map[cell] == '@') // if current cell on the map contains player
{
*pos_x = col; // store player's x coordinate
*pos_y = row; // store player's y coordinate
}
}
}
}
void MoveCharacter(int pos_x, int pos_y, int offset)
{
if(map[PLAYER_POSITION + offset] != '#') // if player doesn't hit the wall
{
if(((map[PLAYER_POSITION + offset] == 'B') || // if player hits the box
(map[PLAYER_POSITION + offset] == 'O')) && // or the box on 'x' cell
(map[PLAYER_POSITION + offset * 2] != '#' || // and box doesn't hit a wall
map[PLAYER_POSITION + offset * 2] != 'B' || // or another box
map[PLAYER_POSITION + offset * 2] != 'O')) // or box on 'x' cell
{
map[PLAYER_POSITION] = ' '; // clear previous player's position
pos_x += offset; // update player's coordinate
if(map[PLAYER_POSITION + offset] == ' ') // if the square next to the box
is empty
map[PLAYER_POSITION + offset] = 'B'; // push the box
else if(map[PLAYER_POSITION + offset] == 'x') // if the square next to the
box is 'x'
map[PLAYER_POSITION + offset] = 'O'; // mark the box is on it's place
else
{
map[PLAYER_POSITION - offset] = '@'; // if box hits the wall or
another box
return; // don't push it any further
}
map[PLAYER_POSITION] = '@'; // draw the player in the new
position
}
else // if the square next to the player is empty
{
map[PLAYER_POSITION] = ' '; // clear previous player position
pos_x += offset; // update player's coordinate
map[PLAYER_POSITION] = '@'; // draw the player in the new
position
}
}
}
int main()
{
int key; // user input key
int pos_x, pos_y; // player's coordinates
int dest_count; // 'x' cells counter
int dest_num = GetDestSquares(); // get number of 'x' cells
printf("%s\n", map); // print map
while(key != 27) // game loop
{
GetPosition(&pos_x, &pos_y); // get player's coordinates
key = getchar(); // get user input
switch(key)
{
// move character up
case 'w':
MoveCharacter(pos_x, pos_y, - MAP_WIDTH - 1);
break;
// move character down
case 's':
MoveCharacter(pos_x, pos_y, MAP_WIDTH + 1);
break;
// move character left
case 'a':
MoveCharacter(pos_x, pos_y, -1);
break;
// move character right
case 'd':
MoveCharacter(pos_x, pos_y, 1);
break;
dest_count = 0; // reset 'x' cells counter
for(int i = 0; i < 10; i++) // for all destination squares
{
if(map[dest_squares[i]] == 'O') dest_count++; // increase 'x' cells counter if box
is on 'x' cell
if(map[dest_squares[i]] == ' ') // if 'x' cell has been erased
map[dest_squares[i]] = 'x'; // restore it
}
printf("%s\n", map); // print map
// if all boxes are on it's places break out of game loop
if(dest_num == dest_count)
{
printf("You win!\n");
key = 27;
}
}
return 0;
}
Relationship between Modules
The game is linked between two files, the first of which is the game file, which is a
file that I use to link it to the other file called sokoban - game, which I mainly use in
the main file of the game that contains the main
Course Design Experience
Learning experience design is a synthesis of numerous design disciplines and the subject of
learning. Interaction design, user experience design, experience design, visual design, and game
design are some of the key design elements employed in LXD. These design ideas are coupled
with educational, training, and development, cognitive psychology, experiential learning,
educational sciences, and neuroscience features.
It is a reality that all we learn originates from. As previously said, an experience is any
circumstance that requires time and makes an imprint. These events do not have to take place in
an instructional context such as a school. They can happen at home, outside, at work, or
anyplace else.
Not every experience is equally educational. Some encounters are simply uninteresting or
irritating. Fortunately, we've all had highly instructional experiences that will last a lifetime.
The ability to create such compelling experiences is the primary characteristic of a competent
LX designer.
Example