├── LICENSE.txt ├── README.md ├── sokoban_game.png └── src ├── com └── zetcode │ ├── Actor.java │ ├── Area.java │ ├── Baggage.java │ ├── Board.java │ ├── Player.java │ ├── Sokoban.java │ └── Wall.java └── resources ├── area.png ├── baggage.png ├── new.txt ├── sokoban.png └── wall.png /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2020, Jan Bodnar 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java-Sokoban-Game 2 | Java Sokoban game source code 3 | 4 | https://zetcode.com/javagames/sokoban/ 5 | 6 | ![Sokoban game screenshot](sokoban_game.png) 7 | -------------------------------------------------------------------------------- /sokoban_game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janbodnar/Java-Sokoban-Game/e8b8b1def62d9406874fd7b0543c1ee37ba2c0f9/sokoban_game.png -------------------------------------------------------------------------------- /src/com/zetcode/Actor.java: -------------------------------------------------------------------------------- 1 | package com.zetcode; 2 | 3 | import java.awt.Image; 4 | 5 | public class Actor { 6 | 7 | private final int SPACE = 20; 8 | 9 | private int x; 10 | private int y; 11 | private Image image; 12 | 13 | public Actor(int x, int y) { 14 | 15 | this.x = x; 16 | this.y = y; 17 | } 18 | 19 | public Image getImage() { 20 | return image; 21 | } 22 | 23 | public void setImage(Image img) { 24 | image = img; 25 | } 26 | 27 | public int x() { 28 | 29 | return x; 30 | } 31 | 32 | public int y() { 33 | 34 | return y; 35 | } 36 | 37 | public void setX(int x) { 38 | 39 | this.x = x; 40 | } 41 | 42 | public void setY(int y) { 43 | 44 | this.y = y; 45 | } 46 | 47 | public boolean isLeftCollision(Actor actor) { 48 | 49 | return x() - SPACE == actor.x() && y() == actor.y(); 50 | } 51 | 52 | public boolean isRightCollision(Actor actor) { 53 | 54 | return x() + SPACE == actor.x() && y() == actor.y(); 55 | } 56 | 57 | public boolean isTopCollision(Actor actor) { 58 | 59 | return y() - SPACE == actor.y() && x() == actor.x(); 60 | } 61 | 62 | public boolean isBottomCollision(Actor actor) { 63 | 64 | return y() + SPACE == actor.y() && x() == actor.x(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/com/zetcode/Area.java: -------------------------------------------------------------------------------- 1 | package com.zetcode; 2 | 3 | import java.awt.Image; 4 | import javax.swing.ImageIcon; 5 | 6 | public class Area extends Actor { 7 | 8 | public Area(int x, int y) { 9 | super(x, y); 10 | 11 | initArea(); 12 | } 13 | 14 | private void initArea() { 15 | 16 | ImageIcon iicon = new ImageIcon("src/resources/area.png"); 17 | Image image = iicon.getImage(); 18 | setImage(image); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/com/zetcode/Baggage.java: -------------------------------------------------------------------------------- 1 | package com.zetcode; 2 | 3 | import java.awt.Image; 4 | import javax.swing.ImageIcon; 5 | 6 | public class Baggage extends Actor { 7 | 8 | public Baggage(int x, int y) { 9 | super(x, y); 10 | 11 | initBaggage(); 12 | } 13 | 14 | private void initBaggage() { 15 | 16 | ImageIcon iicon = new ImageIcon("src/resources/baggage.png"); 17 | Image image = iicon.getImage(); 18 | setImage(image); 19 | } 20 | 21 | public void move(int x, int y) { 22 | 23 | int dx = x() + x; 24 | int dy = y() + y; 25 | 26 | setX(dx); 27 | setY(dy); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/com/zetcode/Board.java: -------------------------------------------------------------------------------- 1 | package com.zetcode; 2 | 3 | import java.awt.Color; 4 | import java.awt.Graphics; 5 | import java.awt.event.KeyAdapter; 6 | import java.awt.event.KeyEvent; 7 | import java.util.ArrayList; 8 | import javax.swing.JPanel; 9 | 10 | public class Board extends JPanel { 11 | 12 | private final int OFFSET = 30; 13 | private final int SPACE = 20; 14 | private final int LEFT_COLLISION = 1; 15 | private final int RIGHT_COLLISION = 2; 16 | private final int TOP_COLLISION = 3; 17 | private final int BOTTOM_COLLISION = 4; 18 | 19 | private ArrayList walls; 20 | private ArrayList baggs; 21 | private ArrayList areas; 22 | 23 | private Player soko; 24 | private int w = 0; 25 | private int h = 0; 26 | 27 | private boolean isCompleted = false; 28 | 29 | private String level 30 | = " ######\n" 31 | + " ## #\n" 32 | + " ##$ #\n" 33 | + " #### $##\n" 34 | + " ## $ $ #\n" 35 | + "#### # ## # ######\n" 36 | + "## # ## ##### ..#\n" 37 | + "## $ $ ..#\n" 38 | + "###### ### #@## ..#\n" 39 | + " ## #########\n" 40 | + " ########\n"; 41 | 42 | public Board() { 43 | 44 | initBoard(); 45 | } 46 | 47 | private void initBoard() { 48 | 49 | addKeyListener(new TAdapter()); 50 | setFocusable(true); 51 | initWorld(); 52 | } 53 | 54 | public int getBoardWidth() { 55 | return this.w; 56 | } 57 | 58 | public int getBoardHeight() { 59 | return this.h; 60 | } 61 | 62 | private void initWorld() { 63 | 64 | walls = new ArrayList<>(); 65 | baggs = new ArrayList<>(); 66 | areas = new ArrayList<>(); 67 | 68 | int x = OFFSET; 69 | int y = OFFSET; 70 | 71 | Wall wall; 72 | Baggage b; 73 | Area a; 74 | 75 | for (int i = 0; i < level.length(); i++) { 76 | 77 | char item = level.charAt(i); 78 | 79 | switch (item) { 80 | 81 | case '\n': 82 | y += SPACE; 83 | 84 | if (this.w < x) { 85 | this.w = x; 86 | } 87 | 88 | x = OFFSET; 89 | break; 90 | 91 | case '#': 92 | wall = new Wall(x, y); 93 | walls.add(wall); 94 | x += SPACE; 95 | break; 96 | 97 | case '$': 98 | b = new Baggage(x, y); 99 | baggs.add(b); 100 | x += SPACE; 101 | break; 102 | 103 | case '.': 104 | a = new Area(x, y); 105 | areas.add(a); 106 | x += SPACE; 107 | break; 108 | 109 | case '@': 110 | soko = new Player(x, y); 111 | x += SPACE; 112 | break; 113 | 114 | case ' ': 115 | x += SPACE; 116 | break; 117 | 118 | default: 119 | break; 120 | } 121 | 122 | h = y; 123 | } 124 | } 125 | 126 | private void buildWorld(Graphics g) { 127 | 128 | g.setColor(new Color(250, 240, 170)); 129 | g.fillRect(0, 0, this.getWidth(), this.getHeight()); 130 | 131 | ArrayList world = new ArrayList<>(); 132 | 133 | world.addAll(walls); 134 | world.addAll(areas); 135 | world.addAll(baggs); 136 | world.add(soko); 137 | 138 | for (int i = 0; i < world.size(); i++) { 139 | 140 | Actor item = world.get(i); 141 | 142 | if (item instanceof Player || item instanceof Baggage) { 143 | 144 | g.drawImage(item.getImage(), item.x() + 2, item.y() + 2, this); 145 | } else { 146 | 147 | g.drawImage(item.getImage(), item.x(), item.y(), this); 148 | } 149 | 150 | if (isCompleted) { 151 | 152 | g.setColor(new Color(0, 0, 0)); 153 | g.drawString("Completed", 25, 20); 154 | } 155 | 156 | } 157 | } 158 | 159 | @Override 160 | public void paintComponent(Graphics g) { 161 | super.paintComponent(g); 162 | 163 | buildWorld(g); 164 | } 165 | 166 | private class TAdapter extends KeyAdapter { 167 | 168 | @Override 169 | public void keyPressed(KeyEvent e) { 170 | 171 | if (isCompleted) { 172 | return; 173 | } 174 | 175 | int key = e.getKeyCode(); 176 | 177 | switch (key) { 178 | 179 | case KeyEvent.VK_LEFT: 180 | 181 | if (checkWallCollision(soko, 182 | LEFT_COLLISION)) { 183 | return; 184 | } 185 | 186 | if (checkBagCollision(LEFT_COLLISION)) { 187 | return; 188 | } 189 | 190 | soko.move(-SPACE, 0); 191 | 192 | break; 193 | 194 | case KeyEvent.VK_RIGHT: 195 | 196 | if (checkWallCollision(soko, RIGHT_COLLISION)) { 197 | return; 198 | } 199 | 200 | if (checkBagCollision(RIGHT_COLLISION)) { 201 | return; 202 | } 203 | 204 | soko.move(SPACE, 0); 205 | 206 | break; 207 | 208 | case KeyEvent.VK_UP: 209 | 210 | if (checkWallCollision(soko, TOP_COLLISION)) { 211 | return; 212 | } 213 | 214 | if (checkBagCollision(TOP_COLLISION)) { 215 | return; 216 | } 217 | 218 | soko.move(0, -SPACE); 219 | 220 | break; 221 | 222 | case KeyEvent.VK_DOWN: 223 | 224 | if (checkWallCollision(soko, BOTTOM_COLLISION)) { 225 | return; 226 | } 227 | 228 | if (checkBagCollision(BOTTOM_COLLISION)) { 229 | return; 230 | } 231 | 232 | soko.move(0, SPACE); 233 | 234 | break; 235 | 236 | case KeyEvent.VK_R: 237 | 238 | restartLevel(); 239 | 240 | break; 241 | 242 | default: 243 | break; 244 | } 245 | 246 | repaint(); 247 | } 248 | } 249 | 250 | private boolean checkWallCollision(Actor actor, int type) { 251 | 252 | switch (type) { 253 | 254 | case LEFT_COLLISION: 255 | 256 | for (int i = 0; i < walls.size(); i++) { 257 | 258 | Wall wall = walls.get(i); 259 | 260 | if (actor.isLeftCollision(wall)) { 261 | 262 | return true; 263 | } 264 | } 265 | 266 | return false; 267 | 268 | case RIGHT_COLLISION: 269 | 270 | for (int i = 0; i < walls.size(); i++) { 271 | 272 | Wall wall = walls.get(i); 273 | 274 | if (actor.isRightCollision(wall)) { 275 | return true; 276 | } 277 | } 278 | 279 | return false; 280 | 281 | case TOP_COLLISION: 282 | 283 | for (int i = 0; i < walls.size(); i++) { 284 | 285 | Wall wall = walls.get(i); 286 | 287 | if (actor.isTopCollision(wall)) { 288 | 289 | return true; 290 | } 291 | } 292 | 293 | return false; 294 | 295 | case BOTTOM_COLLISION: 296 | 297 | for (int i = 0; i < walls.size(); i++) { 298 | 299 | Wall wall = walls.get(i); 300 | 301 | if (actor.isBottomCollision(wall)) { 302 | 303 | return true; 304 | } 305 | } 306 | 307 | return false; 308 | 309 | default: 310 | break; 311 | } 312 | 313 | return false; 314 | } 315 | 316 | private boolean checkBagCollision(int type) { 317 | 318 | switch (type) { 319 | 320 | case LEFT_COLLISION: 321 | 322 | for (int i = 0; i < baggs.size(); i++) { 323 | 324 | Baggage bag = baggs.get(i); 325 | 326 | if (soko.isLeftCollision(bag)) { 327 | 328 | for (int j = 0; j < baggs.size(); j++) { 329 | 330 | Baggage item = baggs.get(j); 331 | 332 | if (!bag.equals(item)) { 333 | 334 | if (bag.isLeftCollision(item)) { 335 | return true; 336 | } 337 | } 338 | 339 | if (checkWallCollision(bag, LEFT_COLLISION)) { 340 | return true; 341 | } 342 | } 343 | 344 | bag.move(-SPACE, 0); 345 | isCompleted(); 346 | } 347 | } 348 | 349 | return false; 350 | 351 | case RIGHT_COLLISION: 352 | 353 | for (int i = 0; i < baggs.size(); i++) { 354 | 355 | Baggage bag = baggs.get(i); 356 | 357 | if (soko.isRightCollision(bag)) { 358 | 359 | for (int j = 0; j < baggs.size(); j++) { 360 | 361 | Baggage item = baggs.get(j); 362 | 363 | if (!bag.equals(item)) { 364 | 365 | if (bag.isRightCollision(item)) { 366 | return true; 367 | } 368 | } 369 | 370 | if (checkWallCollision(bag, RIGHT_COLLISION)) { 371 | return true; 372 | } 373 | } 374 | 375 | bag.move(SPACE, 0); 376 | isCompleted(); 377 | } 378 | } 379 | return false; 380 | 381 | case TOP_COLLISION: 382 | 383 | for (int i = 0; i < baggs.size(); i++) { 384 | 385 | Baggage bag = baggs.get(i); 386 | 387 | if (soko.isTopCollision(bag)) { 388 | 389 | for (int j = 0; j < baggs.size(); j++) { 390 | 391 | Baggage item = baggs.get(j); 392 | 393 | if (!bag.equals(item)) { 394 | 395 | if (bag.isTopCollision(item)) { 396 | return true; 397 | } 398 | } 399 | 400 | if (checkWallCollision(bag, TOP_COLLISION)) { 401 | return true; 402 | } 403 | } 404 | 405 | bag.move(0, -SPACE); 406 | isCompleted(); 407 | } 408 | } 409 | 410 | return false; 411 | 412 | case BOTTOM_COLLISION: 413 | 414 | for (int i = 0; i < baggs.size(); i++) { 415 | 416 | Baggage bag = baggs.get(i); 417 | 418 | if (soko.isBottomCollision(bag)) { 419 | 420 | for (int j = 0; j < baggs.size(); j++) { 421 | 422 | Baggage item = baggs.get(j); 423 | 424 | if (!bag.equals(item)) { 425 | 426 | if (bag.isBottomCollision(item)) { 427 | return true; 428 | } 429 | } 430 | 431 | if (checkWallCollision(bag,BOTTOM_COLLISION)) { 432 | 433 | return true; 434 | } 435 | } 436 | 437 | bag.move(0, SPACE); 438 | isCompleted(); 439 | } 440 | } 441 | 442 | break; 443 | 444 | default: 445 | break; 446 | } 447 | 448 | return false; 449 | } 450 | 451 | public void isCompleted() { 452 | 453 | int nOfBags = baggs.size(); 454 | int finishedBags = 0; 455 | 456 | for (int i = 0; i < nOfBags; i++) { 457 | 458 | Baggage bag = baggs.get(i); 459 | 460 | for (int j = 0; j < nOfBags; j++) { 461 | 462 | Area area = areas.get(j); 463 | 464 | if (bag.x() == area.x() && bag.y() == area.y()) { 465 | 466 | finishedBags += 1; 467 | } 468 | } 469 | } 470 | 471 | if (finishedBags == nOfBags) { 472 | 473 | isCompleted = true; 474 | repaint(); 475 | } 476 | } 477 | 478 | private void restartLevel() { 479 | 480 | areas.clear(); 481 | baggs.clear(); 482 | walls.clear(); 483 | 484 | initWorld(); 485 | 486 | if (isCompleted) { 487 | isCompleted = false; 488 | } 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /src/com/zetcode/Player.java: -------------------------------------------------------------------------------- 1 | package com.zetcode; 2 | 3 | import java.awt.Image; 4 | import javax.swing.ImageIcon; 5 | 6 | public class Player extends Actor { 7 | 8 | public Player(int x, int y) { 9 | super(x, y); 10 | 11 | initPlayer(); 12 | } 13 | 14 | private void initPlayer() { 15 | 16 | ImageIcon iicon = new ImageIcon("src/resources/sokoban.png"); 17 | Image image = iicon.getImage(); 18 | setImage(image); 19 | } 20 | 21 | public void move(int x, int y) { 22 | 23 | int dx = x() + x; 24 | int dy = y() + y; 25 | 26 | setX(dx); 27 | setY(dy); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/com/zetcode/Sokoban.java: -------------------------------------------------------------------------------- 1 | package com.zetcode; 2 | 3 | import java.awt.EventQueue; 4 | import javax.swing.JFrame; 5 | 6 | public class Sokoban extends JFrame { 7 | 8 | private final int OFFSET = 30; 9 | 10 | public Sokoban() { 11 | 12 | initUI(); 13 | } 14 | 15 | private void initUI() { 16 | 17 | Board board = new Board(); 18 | add(board); 19 | 20 | setTitle("Sokoban"); 21 | 22 | setSize(board.getBoardWidth() + OFFSET, 23 | board.getBoardHeight() + 2 * OFFSET); 24 | 25 | setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 26 | setLocationRelativeTo(null); 27 | } 28 | 29 | public static void main(String[] args) { 30 | 31 | EventQueue.invokeLater(() -> { 32 | 33 | Sokoban game = new Sokoban(); 34 | game.setVisible(true); 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/com/zetcode/Wall.java: -------------------------------------------------------------------------------- 1 | package com.zetcode; 2 | 3 | import java.awt.Image; 4 | import javax.swing.ImageIcon; 5 | 6 | public class Wall extends Actor { 7 | 8 | private Image image; 9 | 10 | public Wall(int x, int y) { 11 | super(x, y); 12 | 13 | initWall(); 14 | } 15 | 16 | private void initWall() { 17 | 18 | ImageIcon iicon = new ImageIcon("src/resources/wall.png"); 19 | image = iicon.getImage(); 20 | setImage(image); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/resources/area.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janbodnar/Java-Sokoban-Game/e8b8b1def62d9406874fd7b0543c1ee37ba2c0f9/src/resources/area.png -------------------------------------------------------------------------------- /src/resources/baggage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janbodnar/Java-Sokoban-Game/e8b8b1def62d9406874fd7b0543c1ee37ba2c0f9/src/resources/baggage.png -------------------------------------------------------------------------------- /src/resources/new.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/resources/sokoban.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janbodnar/Java-Sokoban-Game/e8b8b1def62d9406874fd7b0543c1ee37ba2c0f9/src/resources/sokoban.png -------------------------------------------------------------------------------- /src/resources/wall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janbodnar/Java-Sokoban-Game/e8b8b1def62d9406874fd7b0543c1ee37ba2c0f9/src/resources/wall.png --------------------------------------------------------------------------------