kaka.farm

Unnamed repository; edit this file 'description' to name the repository.
git clone https://kaka.farm/~git/kaka.farm
Log | Files | Refs | README

sokoban.js (6107B)


      1 'use strict';
      2 
      3 
      4 console.log('sokoban.js start.');
      5 
      6 
      7 let sokoban = (function() {
      8 	let levels = [
      9 		`
     10 ##############
     11 #.@...*....z.#
     12 ##############
     13 `,
     14 		`
     15     #####
     16     #...#
     17     #*..#
     18   ###..*##
     19   #..*.*.#
     20 ###.#.##.#   ######
     21 #...#.##.#####..zz#
     22 #.*..*..........zz#
     23 #####.###.#@##..zz#
     24     #.....#########
     25     #######
     26 `,
     27 	];
     28 	let level_win_test_not_all_endings = `
     29     #####
     30     #...#
     31     #...#
     32   ###...##
     33   #......#
     34 ###.#.##.#   ######
     35 #...#.##.#####..zz#
     36 #..z.*..........zz#
     37 #####.###.#@##..zz#
     38     #.....#########
     39     #######
     40 `;
     41 	let level_win_test_all_endings = `
     42     #####
     43     #...#
     44     #...#
     45   ###...##
     46   #......#
     47 ###.#.##.#   ######
     48 #...#.##.#####....#
     49 #..z.*............#
     50 #####.###.#@##....#
     51     #.....#########
     52     #######
     53 `;
     54 
     55 	function read_level_text(s) {
     56 		let state = {
     57 			rocks: [],
     58 			walls: [],
     59 			floors: [],
     60 			endings: [],
     61 		};
     62 		let lines = s.split('\n');
     63 		let max_x = Math.max(...lines.map(line=>line.length));
     64 		for (let y = 0; y < lines.length; y++) {
     65 			state.walls[y] = [];
     66 			state.rocks[y] = [];
     67 			state.floors[y] = [];
     68 			state.endings[y] = [];
     69 			for (let x = 0; x < lines[y].length; x++) {
     70 				if (lines[y][x] == '@') {
     71 					state.player_position = [x, y];
     72 					state.floors[y][x] = '.';
     73 				} else if (lines[y][x] == '*') {
     74 					state.rocks[y][x] = '*';
     75 					state.floors[y][x] = '.';
     76 				} else if (lines[y][x] == '#') {
     77 					state.walls[y][x] = '#';
     78 				} else if (lines[y][x] == 'z') {
     79 					state.endings[y][x] = 'z';
     80 				} else if (lines[y][x] == '.') {
     81 					state.floors[y][x] = '.';
     82 				};
     83 			};
     84 		};
     85 
     86 		return state;
     87 	};
     88 
     89 	function here_player(state, [x, y]) {
     90 		return (
     91 			state.player_position[0] == x &&
     92 			state.player_position[1] == y
     93 		);
     94 	}
     95 	function here_wall(state, [x, y]) {
     96 		return state.walls[y][x] == '#';
     97 	}
     98 	function here_rock(state, [x, y]) {
     99 		return state.rocks[y][x] == '*';
    100 	}
    101 	function here_ending(state, [x, y]) {
    102 		return state.endings[y][x] == 'z';
    103 	}
    104 	function here_floor(state, [x, y]) {
    105 		return state.floors[y][x] == '.';
    106 	}
    107 	function move_rock(state, [fx, fy], [tx, ty]) {
    108 		delete state.rocks[fy][fx];
    109 		state.rocks[ty][tx] = '*';
    110 	}
    111 
    112 
    113 	function is_won(game) {
    114 		for (let y = 0; y < game.level_state.rocks.length; y++) {
    115 			if (game.level_state.rocks[y] != undefined) {
    116 				for (let x = 0; x < game.level_state.rocks[y].length; x++) {
    117 					if (here_rock(game.level_state, [x, y]) != here_ending(game.level_state, [x, y])) {
    118 						return false;
    119 					}
    120 				}
    121 			}
    122 		}
    123 		return true;
    124 	};
    125 
    126 
    127 	function copy_level_state(game) {
    128 		let state_copy = [
    129 			{},
    130 			'rocks',
    131 			'floors',
    132 			'walls',
    133 			'endings',
    134 		].reduce((acc, entity_name)=>{
    135 			acc[entity_name] = (
    136 				game
    137 				.level_state[entity_name]
    138 				.map(line=>line.map(c=>c))
    139 			);
    140 			return acc;
    141 		});
    142 		state_copy.player_position = game.level_state.player_position.map(p=>p);
    143 		return state_copy;
    144 	};
    145 
    146 
    147 	function action_step(game, direction) {
    148 		if (!is_won(game)) {
    149 			let p0 = game.level_state.player_position;
    150 			let p1 = [
    151 				p0[0] + direction[0],
    152 				p0[1] + direction[1]];
    153 			let p2 = [
    154 				p1[0] + direction[0],
    155 				p1[1] + direction[1]];
    156 
    157 			let is_blocked = (
    158 				here_wall(game.level_state, p1) || (
    159 					here_rock(game.level_state, p1) && (
    160 						here_rock(game.level_state, p2) ||
    161 						here_wall(game.level_state, p2))));
    162 			let is_pushing = (
    163 				here_rock(game.level_state, p1) &&
    164 				!here_rock(game.level_state, p2));
    165 
    166 			if (!is_blocked) {
    167 				game.number_of_steps += 1;
    168 				let old_state = copy_level_state(game);
    169 				game.undo_stack.push(old_state);
    170 				if (is_pushing) {
    171 					move_rock(game.level_state, p1, p2);
    172 				};
    173 				game.level_state.player_position = p1;
    174 			};
    175 		};
    176 	};
    177 
    178 	function action_step_up(game) { action_step(game, [0, -1]); };
    179 	function action_step_down(game) { action_step(game, [0, 1]); };
    180 	function action_step_left(game) { action_step(game, [-1, 0]); };
    181 	function action_step_right(game) { action_step(game, [1, 0]); };
    182 
    183 
    184 	function action_next_level(game) {
    185 		if (is_won(game) && game.current_level_number < levels.length) {
    186 			game.number_of_steps = 0;
    187 			game.undo_stack = [];
    188 			game.current_level_number += 1;
    189 			game.level_state = read_level_text(levels[game.current_level_number]);
    190 		};
    191 	};
    192 
    193 
    194 	function action_restart_level(game) {
    195 		game.number_of_steps = 0;
    196 		game.undo_stack = [];
    197 		game.level_state = read_level_text(levels[game.current_level_number]);
    198 	};
    199 
    200 
    201 	function action_undo(game) {
    202 		if (game.undo_stack.length > 0) {
    203 			game.number_of_steps -= 1;
    204 			game.level_state = game.undo_stack.pop();
    205 		}
    206 	}
    207 
    208 
    209 	function game_to_text(game) {
    210 		console.log(game.undo_state);
    211 		let s = '';
    212 
    213 		if (is_won(game)) {
    214 			s = s.concat('Won! Press c to continue.');
    215 		}
    216 		s = s.concat(`Level ${game.current_level_number + 1}. Step ${game.number_of_steps}.`);
    217 
    218 		let max_y = Math.max(
    219 			game.level_state.rocks.length,
    220 			game.level_state.endings.length,
    221 			game.level_state.floors.length,
    222 			game.level_state.walls.length,
    223 			game.level_state.player_position[1],
    224 		);
    225 		for (let y = 0; y < max_y; y++) {
    226 			let max_x = Math.max(
    227 				game.level_state.rocks[y].length,
    228 				game.level_state.endings[y].length,
    229 				game.level_state.floors[y].length,
    230 				game.level_state.walls[y].length,
    231 				game.level_state.player_position[0],
    232 			);
    233 			for (let x = 0; x < max_x; x++) {
    234 				if (here_player(game.level_state, [x, y])) {
    235 					s = s.concat('@');
    236 				} else if (here_wall(game.level_state, [x, y])) {
    237 					s = s.concat('#');
    238 				} else if (here_rock(game.level_state, [x, y])) {
    239 					s = s.concat('*');
    240 				} else if (here_floor(game.level_state, [x, y])) {
    241 					s = s.concat('.');
    242 				} else if (here_ending(game.level_state, [x, y])) {
    243 					s = s.concat('z');
    244 				} else {
    245 					s = s.concat(' ');
    246 				}
    247 			};
    248 			s = s.concat('\n');
    249 		};
    250 
    251 		return s.trimEnd();
    252 	};
    253 
    254 
    255 	function new_game() {
    256 		return {
    257 			current_level_number: 0,
    258 			level_state: read_level_text(levels[0]),
    259 			undo_stack: [],
    260 			number_of_steps: 0,
    261 		};
    262 	};
    263 
    264 
    265 	return {
    266 		action_next_level,
    267 		action_restart_level,
    268 		action_step_down,
    269 		action_step_left,
    270 		action_step_right,
    271 		action_step_up,
    272 		action_undo,
    273 		game_to_text,
    274 		new_game,
    275 	};
    276 })();
    277 
    278 
    279 console.log('sokoban.js end.');