Data commit
This commit is contained in:
parent
7387c8f97b
commit
cb5bb5e222
199093 changed files with 3378972 additions and 0 deletions
3
Task/Sokoban/00-META.yaml
Normal file
3
Task/Sokoban/00-META.yaml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
from: http://rosettacode.org/wiki/Sokoban
|
||||
note: Games
|
||||
26
Task/Sokoban/00-TASK.txt
Normal file
26
Task/Sokoban/00-TASK.txt
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
Demonstrate how to find a solution to a given [[wp:Sokoban|Sokoban]] level. For the purpose of this task (formally, a PSPACE-complete problem) any method may be used. However a move-optimal or push-optimal (or any other -optimal) solutions is preferred.
|
||||
|
||||
Sokoban levels are usually stored as a character array where
|
||||
* ''space'' is an empty square
|
||||
* # is a wall
|
||||
* @ is the player
|
||||
* $ is a box
|
||||
* . is a goal
|
||||
* + is the player on a goal
|
||||
* * is a box on a goal
|
||||
<syntaxhighlight lang="text">#######
|
||||
# #
|
||||
# #
|
||||
#. # #
|
||||
#. $$ #
|
||||
#.$$ #
|
||||
#.# @#
|
||||
#######</syntaxhighlight>
|
||||
|
||||
Sokoban solutions are usually stored in the LURD format, where lowercase l, u, r and d represent a move in that ('''l'''eft, '''u'''p, '''r'''ight, '''d'''own) direction and capital LURD represents a push.
|
||||
|
||||
Please state if you use some other format for either the input or output, and why.
|
||||
|
||||
For more information, see [http://www.sokobano.de/wiki/index.php?title=Main_Page the Sokoban wiki].
|
||||
|
||||
|
||||
87
Task/Sokoban/11l/sokoban.11l
Normal file
87
Task/Sokoban/11l/sokoban.11l
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
[String] data
|
||||
V nrows = 0
|
||||
V px = 0
|
||||
V py = 0
|
||||
V sdata = ‘’
|
||||
V ddata = ‘’
|
||||
|
||||
F init(board)
|
||||
:data = board.split("\n")
|
||||
:nrows = max(:data.map(r -> r.len))
|
||||
|
||||
V maps = [‘ ’ = ‘ ’, ‘.’ = ‘.’, ‘@’ = ‘ ’, ‘#’ = ‘#’, ‘$’ = ‘ ’]
|
||||
V mapd = [‘ ’ = ‘ ’, ‘.’ = ‘ ’, ‘@’ = ‘@’, ‘#’ = ‘ ’, ‘$’ = ‘*’]
|
||||
|
||||
L(row) :data
|
||||
V r = L.index
|
||||
L(ch) row
|
||||
V c = L.index
|
||||
:sdata ‘’= maps[ch]
|
||||
:ddata ‘’= mapd[ch]
|
||||
I ch == ‘@’
|
||||
:px = c
|
||||
:py = r
|
||||
|
||||
F push(x, y, dx, dy, &data)
|
||||
I :sdata[(y + 2 * dy) * :nrows + x + 2 * dx] == ‘#’
|
||||
| data[(y + 2 * dy) * :nrows + x + 2 * dx] != ‘ ’
|
||||
data = ‘’
|
||||
R
|
||||
|
||||
data[y * :nrows + x] = ‘ ’
|
||||
data[(y + dy) * :nrows + x + dx] = ‘@’
|
||||
data[(y + 2 * dy) * :nrows + x + 2 * dx] = ‘*’
|
||||
|
||||
F is_solved(data)
|
||||
L(i) 0 .< data.len
|
||||
I (:sdata[i] == ‘.’) != (data[i] == ‘*’)
|
||||
R 0B
|
||||
R 1B
|
||||
|
||||
F solve()
|
||||
V open = Deque([(:ddata, ‘’, :px, :py)])
|
||||
V visited = Set([:ddata])
|
||||
V dirs = ((0, -1, ‘u’, ‘U’), ( 1, 0, ‘r’, ‘R’),
|
||||
(0, 1, ‘d’, ‘D’), (-1, 0, ‘l’, ‘L’))
|
||||
|
||||
L !open.empty
|
||||
V (cur, csol, x, y) = open.pop_left()
|
||||
|
||||
L(di) dirs
|
||||
V temp = copy(cur)
|
||||
V (dx, dy) = (di[0], di[1])
|
||||
|
||||
I temp[(y + dy) * :nrows + x + dx] == ‘*’
|
||||
push(x, y, dx, dy, &temp)
|
||||
I temp != ‘’ & temp !C visited
|
||||
I is_solved(temp)
|
||||
R csol‘’di[3]
|
||||
open.append((temp, csol‘’di[3], x + dx, y + dy))
|
||||
visited.add(temp)
|
||||
E
|
||||
I :sdata[(y + dy) * :nrows + x + dx] == ‘#’ | temp[(y + dy) * :nrows + x + dx] != ‘ ’
|
||||
L.continue
|
||||
|
||||
temp[y * :nrows + x] = ‘ ’
|
||||
temp[(y + dy) * :nrows + x + dx] = ‘@’
|
||||
|
||||
I temp !C visited
|
||||
I is_solved(temp)
|
||||
R csol‘’di[2]
|
||||
open.append((temp, csol‘’di[2], x + dx, y + dy))
|
||||
visited.add(temp)
|
||||
|
||||
R ‘No solution’
|
||||
|
||||
V level =
|
||||
|‘#######
|
||||
# #
|
||||
# #
|
||||
#. # #
|
||||
#. $$ #
|
||||
#.$$ #
|
||||
#.# @#
|
||||
#######’
|
||||
|
||||
init(level)
|
||||
print(level"\n\n"solve())
|
||||
25
Task/Sokoban/Befunge/sokoban.bf
Normal file
25
Task/Sokoban/Befunge/sokoban.bf
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
589*+0g"0"-25**689*+0g"0"-+50p v # Sokoban - (c) Matthew Westcott 2000 # 03
|
||||
83*>10p99*2->:00p10gg"x"-#v_v p01g<> ## # # # # # # # # # # # # # # # # # # #
|
||||
<< ^ -1g01_^#!: -1g00< > v # # # # # # # # # # # # # # # # # # # # #
|
||||
|-"8"_v#-"2":_v#-"6":_v#-"4":~< <0 #
|
||||
> 1 0>$1+2 0>$2+3%\0>$1+3%40v $4<#
|
||||
v -"0"gp04:-1+g<v:-1+g00p03:p < $p #
|
||||
>#v_"X">30g40gv0>40g10g+1-g:"#"-!|0 #
|
||||
0>"x" ^v1g00p<^1g04p03:_v#`\ "9"<0 #
|
||||
v:g-2++<>0gg"X"-#v_"0">00 g10gp30g^ #
|
||||
4# ^g04g04g0<>" " ^v3< #
|
||||
>8*-#v_$"o" v ^1-1+g0< >00g30g30v #
|
||||
# >"0"-#v_v> ^v01-2++g< #
|
||||
^>#$" " 0#<v>"@"50g1-50p^>g40g40gv #
|
||||
v_^#!-"@"g-1+g04g01:-1+g03g00p-2++< #
|
||||
>"0"50g1+50p>\10g40g+1-p30g02050g |#
|
||||
>,#-:#3_@#"a\rx#glg#lw$Zhoo#Grqh$"0<#
|
||||
#####################################
|
||||
########
|
||||
# #
|
||||
# o@0o #
|
||||
# #
|
||||
# o#
|
||||
### ###
|
||||
#0 x 0#
|
||||
########
|
||||
154
Task/Sokoban/C++/sokoban-1.cpp
Normal file
154
Task/Sokoban/C++/sokoban-1.cpp
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
#include <tuple>
|
||||
#include <set>
|
||||
#include <array>
|
||||
using namespace std;
|
||||
|
||||
class Board
|
||||
{
|
||||
public:
|
||||
vector<vector<char>> sData, dData;
|
||||
int px, py;
|
||||
|
||||
Board(string b)
|
||||
{
|
||||
regex pattern("([^\\n]+)\\n?");
|
||||
sregex_iterator end, iter(b.begin(), b.end(), pattern);
|
||||
|
||||
int w = 0;
|
||||
vector<string> data;
|
||||
for(; iter != end; ++iter){
|
||||
data.push_back((*iter)[1]);
|
||||
w = max(w, (*iter)[1].length());
|
||||
}
|
||||
|
||||
for(int v = 0; v < data.size(); ++v){
|
||||
vector<char> sTemp, dTemp;
|
||||
for(int u = 0; u < w; ++u){
|
||||
if(u > data[v].size()){
|
||||
sTemp.push_back(' ');
|
||||
dTemp.push_back(' ');
|
||||
}else{
|
||||
char s = ' ', d = ' ', c = data[v][u];
|
||||
|
||||
if(c == '#')
|
||||
s = '#';
|
||||
else if(c == '.' || c == '*' || c == '+')
|
||||
s = '.';
|
||||
|
||||
if(c == '@' || c == '+'){
|
||||
d = '@';
|
||||
px = u;
|
||||
py = v;
|
||||
}else if(c == '$' || c == '*')
|
||||
d = '*';
|
||||
|
||||
sTemp.push_back(s);
|
||||
dTemp.push_back(d);
|
||||
}
|
||||
}
|
||||
|
||||
sData.push_back(sTemp);
|
||||
dData.push_back(dTemp);
|
||||
}
|
||||
}
|
||||
|
||||
bool move(int x, int y, int dx, int dy, vector<vector<char>> &data)
|
||||
{
|
||||
if(sData[y+dy][x+dx] == '#' || data[y+dy][x+dx] != ' ')
|
||||
return false;
|
||||
|
||||
data[y][x] = ' ';
|
||||
data[y+dy][x+dx] = '@';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool push(int x, int y, int dx, int dy, vector<vector<char>> &data)
|
||||
{
|
||||
if(sData[y+2*dy][x+2*dx] == '#' || data[y+2*dy][x+2*dx] != ' ')
|
||||
return false;
|
||||
|
||||
data[y][x] = ' ';
|
||||
data[y+dy][x+dx] = '@';
|
||||
data[y+2*dy][x+2*dx] = '*';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isSolved(const vector<vector<char>> &data)
|
||||
{
|
||||
for(int v = 0; v < data.size(); ++v)
|
||||
for(int u = 0; u < data[v].size(); ++u)
|
||||
if((sData[v][u] == '.') ^ (data[v][u] == '*'))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
string solve()
|
||||
{
|
||||
set<vector<vector<char>>> visited;
|
||||
queue<tuple<vector<vector<char>>, string, int, int>> open;
|
||||
|
||||
open.push(make_tuple(dData, "", px, py));
|
||||
visited.insert(dData);
|
||||
|
||||
array<tuple<int, int, char, char>, 4> dirs;
|
||||
dirs[0] = make_tuple(0, -1, 'u', 'U');
|
||||
dirs[1] = make_tuple(1, 0, 'r', 'R');
|
||||
dirs[2] = make_tuple(0, 1, 'd', 'D');
|
||||
dirs[3] = make_tuple(-1, 0, 'l', 'L');
|
||||
|
||||
while(open.size() > 0){
|
||||
vector<vector<char>> temp, cur = get<0>(open.front());
|
||||
string cSol = get<1>(open.front());
|
||||
int x = get<2>(open.front());
|
||||
int y = get<3>(open.front());
|
||||
open.pop();
|
||||
|
||||
for(int i = 0; i < 4; ++i){
|
||||
temp = cur;
|
||||
int dx = get<0>(dirs[i]);
|
||||
int dy = get<1>(dirs[i]);
|
||||
|
||||
if(temp[y+dy][x+dx] == '*'){
|
||||
if(push(x, y, dx, dy, temp) && (visited.find(temp) == visited.end())){
|
||||
if(isSolved(temp))
|
||||
return cSol + get<3>(dirs[i]);
|
||||
open.push(make_tuple(temp, cSol + get<3>(dirs[i]), x+dx, y+dy));
|
||||
visited.insert(temp);
|
||||
}
|
||||
}else if(move(x, y, dx, dy, temp) && (visited.find(temp) == visited.end())){
|
||||
if(isSolved(temp))
|
||||
return cSol + get<2>(dirs[i]);
|
||||
open.push(make_tuple(temp, cSol + get<2>(dirs[i]), x+dx, y+dy));
|
||||
visited.insert(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "No solution";
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
string level =
|
||||
"#######\n"
|
||||
"# #\n"
|
||||
"# #\n"
|
||||
"#. # #\n"
|
||||
"#. $$ #\n"
|
||||
"#.$$ #\n"
|
||||
"#.# @#\n"
|
||||
"#######";
|
||||
|
||||
Board b(level);
|
||||
|
||||
cout << level << endl << endl << b.solve() << endl;
|
||||
return 0;
|
||||
}
|
||||
140
Task/Sokoban/C++/sokoban-2.cpp
Normal file
140
Task/Sokoban/C++/sokoban-2.cpp
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <tuple>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/unordered_set.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
typedef vector<char> TableRow;
|
||||
typedef vector<TableRow> Table;
|
||||
|
||||
struct Board {
|
||||
Table sData, dData;
|
||||
int px, py;
|
||||
|
||||
Board(string b) {
|
||||
vector<string> data;
|
||||
boost::split(data, b, boost::is_any_of("\n"));
|
||||
|
||||
size_t width = 0;
|
||||
for (auto &row: data)
|
||||
width = max(width, row.size());
|
||||
|
||||
map<char,char> maps = {{' ',' '}, {'.','.'}, {'@',' '},
|
||||
{'#','#'}, {'$',' '}},
|
||||
mapd = {{' ',' '}, {'.',' '}, {'@','@'},
|
||||
{'#',' '}, {'$','*'}};
|
||||
|
||||
for (size_t r = 0; r < data.size(); r++) {
|
||||
TableRow sTemp, dTemp;
|
||||
for (size_t c = 0; c < width; c++) {
|
||||
char ch = c < data[r].size() ? data[r][c] : ' ';
|
||||
sTemp.push_back(maps[ch]);
|
||||
dTemp.push_back(mapd[ch]);
|
||||
if (ch == '@') {
|
||||
px = c;
|
||||
py = r;
|
||||
}
|
||||
}
|
||||
sData.push_back(sTemp);
|
||||
dData.push_back(dTemp);
|
||||
}
|
||||
}
|
||||
|
||||
bool move(int x, int y, int dx, int dy, Table &data) {
|
||||
if (sData[y+dy][x+dx] == '#' || data[y+dy][x+dx] != ' ')
|
||||
return false;
|
||||
|
||||
data[y][x] = ' ';
|
||||
data[y+dy][x+dx] = '@';
|
||||
return true;
|
||||
}
|
||||
|
||||
bool push(int x, int y, int dx, int dy, Table &data) {
|
||||
if (sData[y+2*dy][x+2*dx] == '#' || data[y+2*dy][x+2*dx] != ' ')
|
||||
return false;
|
||||
|
||||
data[y][x] = ' ';
|
||||
data[y+dy][x+dx] = '@';
|
||||
data[y+2*dy][x+2*dx] = '*';
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isSolved(const Table &data) {
|
||||
for (size_t r = 0; r < data.size(); r++)
|
||||
for (size_t c = 0; c < data[r].size(); c++)
|
||||
if ((sData[r][c] == '.') != (data[r][c] == '*'))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
string solve() {
|
||||
boost::unordered_set<Table, boost::hash<Table>> visited;
|
||||
visited.insert(dData);
|
||||
|
||||
queue<tuple<Table, string, int, int>> open;
|
||||
open.push(make_tuple(dData, "", px, py));
|
||||
|
||||
vector<tuple<int, int, char, char>> dirs = {
|
||||
make_tuple( 0, -1, 'u', 'U'),
|
||||
make_tuple( 1, 0, 'r', 'R'),
|
||||
make_tuple( 0, 1, 'd', 'D'),
|
||||
make_tuple(-1, 0, 'l', 'L')
|
||||
};
|
||||
|
||||
while (open.size() > 0) {
|
||||
Table temp, cur = get<0>(open.front());
|
||||
string cSol = get<1>(open.front());
|
||||
int x = get<2>(open.front());
|
||||
int y = get<3>(open.front());
|
||||
open.pop();
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
temp = cur;
|
||||
int dx = get<0>(dirs[i]);
|
||||
int dy = get<1>(dirs[i]);
|
||||
|
||||
if (temp[y+dy][x+dx] == '*') {
|
||||
if (push(x, y, dx, dy, temp) &&
|
||||
visited.find(temp) == visited.end()) {
|
||||
if (isSolved(temp))
|
||||
return cSol + get<3>(dirs[i]);
|
||||
open.push(make_tuple(temp, cSol + get<3>(dirs[i]),
|
||||
x+dx, y+dy));
|
||||
visited.insert(temp);
|
||||
}
|
||||
} else if (move(x, y, dx, dy, temp) &&
|
||||
visited.find(temp) == visited.end()) {
|
||||
if (isSolved(temp))
|
||||
return cSol + get<2>(dirs[i]);
|
||||
open.push(make_tuple(temp, cSol + get<2>(dirs[i]),
|
||||
x+dx, y+dy));
|
||||
visited.insert(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "No solution";
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
string level = "#######\n"
|
||||
"# #\n"
|
||||
"# #\n"
|
||||
"#. # #\n"
|
||||
"#. $$ #\n"
|
||||
"#.$$ #\n"
|
||||
"#.# @#\n"
|
||||
"#######";
|
||||
|
||||
cout << level << endl << endl;
|
||||
Board board(level);
|
||||
cout << board.solve() << endl;
|
||||
return 0;
|
||||
}
|
||||
174
Task/Sokoban/C-sharp/sokoban.cs
Normal file
174
Task/Sokoban/C-sharp/sokoban.cs
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SokobanSolver
|
||||
{
|
||||
public class SokobanSolver
|
||||
{
|
||||
private class Board
|
||||
{
|
||||
public string Cur { get; internal set; }
|
||||
public string Sol { get; internal set; }
|
||||
public int X { get; internal set; }
|
||||
public int Y { get; internal set; }
|
||||
|
||||
public Board(string cur, string sol, int x, int y)
|
||||
{
|
||||
Cur = cur;
|
||||
Sol = sol;
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
}
|
||||
|
||||
private string destBoard, currBoard;
|
||||
private int playerX, playerY, nCols;
|
||||
|
||||
SokobanSolver(string[] board)
|
||||
{
|
||||
nCols = board[0].Length;
|
||||
StringBuilder destBuf = new StringBuilder();
|
||||
StringBuilder currBuf = new StringBuilder();
|
||||
|
||||
for (int r = 0; r < board.Length; r++)
|
||||
{
|
||||
for (int c = 0; c < nCols; c++)
|
||||
{
|
||||
|
||||
char ch = board[r][c];
|
||||
|
||||
destBuf.Append(ch != '$' && ch != '@' ? ch : ' ');
|
||||
currBuf.Append(ch != '.' ? ch : ' ');
|
||||
|
||||
if (ch == '@')
|
||||
{
|
||||
this.playerX = c;
|
||||
this.playerY = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
destBoard = destBuf.ToString();
|
||||
currBoard = currBuf.ToString();
|
||||
}
|
||||
|
||||
private string Move(int x, int y, int dx, int dy, string trialBoard)
|
||||
{
|
||||
|
||||
int newPlayerPos = (y + dy) * nCols + x + dx;
|
||||
|
||||
if (trialBoard[newPlayerPos] != ' ')
|
||||
return null;
|
||||
|
||||
char[] trial = trialBoard.ToCharArray();
|
||||
trial[y * nCols + x] = ' ';
|
||||
trial[newPlayerPos] = '@';
|
||||
|
||||
return new string(trial);
|
||||
}
|
||||
|
||||
private string Push(int x, int y, int dx, int dy, string trialBoard)
|
||||
{
|
||||
|
||||
int newBoxPos = (y + 2 * dy) * nCols + x + 2 * dx;
|
||||
|
||||
if (trialBoard[newBoxPos] != ' ')
|
||||
return null;
|
||||
|
||||
char[] trial = trialBoard.ToCharArray();
|
||||
trial[y * nCols + x] = ' ';
|
||||
trial[(y + dy) * nCols + x + dx] = '@';
|
||||
trial[newBoxPos] = '$';
|
||||
|
||||
return new string(trial);
|
||||
}
|
||||
|
||||
private bool IsSolved(string trialBoard)
|
||||
{
|
||||
for (int i = 0; i < trialBoard.Length; i++)
|
||||
if ((destBoard[i] == '.')
|
||||
!= (trialBoard[i] == '$'))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private string Solve()
|
||||
{
|
||||
char[,] dirLabels = { { 'u', 'U' }, { 'r', 'R' }, { 'd', 'D' }, { 'l', 'L' } };
|
||||
int[,] dirs = { { 0, -1 }, { 1, 0 }, { 0, 1 }, { -1, 0 } };
|
||||
ISet<string> history = new HashSet<string>();
|
||||
LinkedList<Board> open = new LinkedList<Board>();
|
||||
|
||||
history.Add(currBoard);
|
||||
open.AddLast(new Board(currBoard, string.Empty, playerX, playerY));
|
||||
|
||||
while (!open.Count.Equals(0))
|
||||
{
|
||||
Board item = open.First();
|
||||
open.RemoveFirst();
|
||||
string cur = item.Cur;
|
||||
string sol = item.Sol;
|
||||
int x = item.X;
|
||||
int y = item.Y;
|
||||
|
||||
for (int i = 0; i < dirs.GetLength(0); i++)
|
||||
{
|
||||
string trial = cur;
|
||||
int dx = dirs[i, 0];
|
||||
int dy = dirs[i, 1];
|
||||
|
||||
// are we standing next to a box ?
|
||||
if (trial[(y + dy) * nCols + x + dx] == '$')
|
||||
{
|
||||
// can we push it ?
|
||||
if ((trial = Push(x, y, dx, dy, trial)) != null)
|
||||
{
|
||||
// or did we already try this one ?
|
||||
if (!history.Contains(trial))
|
||||
{
|
||||
|
||||
string newSol = sol + dirLabels[i, 1];
|
||||
|
||||
if (IsSolved(trial))
|
||||
return newSol;
|
||||
|
||||
open.AddLast(new Board(trial, newSol, x + dx, y + dy));
|
||||
history.Add(trial);
|
||||
}
|
||||
}
|
||||
// otherwise try changing position
|
||||
}
|
||||
else if ((trial = Move(x, y, dx, dy, trial)) != null)
|
||||
{
|
||||
if (!history.Contains(trial))
|
||||
{
|
||||
string newSol = sol + dirLabels[i, 0];
|
||||
open.AddLast(new Board(trial, newSol, x + dx, y + dy));
|
||||
history.Add(trial);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "No solution";
|
||||
}
|
||||
|
||||
public static void Main(string[] a)
|
||||
{
|
||||
string level = "#######," +
|
||||
"# #," +
|
||||
"# #," +
|
||||
"#. # #," +
|
||||
"#. $$ #," +
|
||||
"#.$$ #," +
|
||||
"#.# @#," +
|
||||
"#######";
|
||||
System.Console.WriteLine("Level:\n");
|
||||
foreach (string line in level.Split(','))
|
||||
{
|
||||
System.Console.WriteLine(line);
|
||||
}
|
||||
System.Console.WriteLine("\nSolution:\n");
|
||||
System.Console.WriteLine(new SokobanSolver(level.Split(',')).Solve());
|
||||
}
|
||||
}
|
||||
}
|
||||
402
Task/Sokoban/C/sokoban.c
Normal file
402
Task/Sokoban/C/sokoban.c
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
int w, h, n_boxes;
|
||||
uint8_t *board, *goals, *live;
|
||||
|
||||
typedef uint16_t cidx_t;
|
||||
typedef uint32_t hash_t;
|
||||
|
||||
/* board configuration is represented by an array of cell indices
|
||||
of player and boxes */
|
||||
typedef struct state_t state_t;
|
||||
|
||||
struct state_t { // variable length
|
||||
hash_t h;
|
||||
state_t *prev, *next, *qnext;
|
||||
cidx_t c[];
|
||||
};
|
||||
|
||||
size_t state_size, block_size = 32;
|
||||
state_t *block_root, *block_head;
|
||||
|
||||
inline
|
||||
state_t* newstate(state_t *parent) {
|
||||
inline state_t* next_of(state_t *s) {
|
||||
return (void*)((uint8_t*)s + state_size);
|
||||
}
|
||||
|
||||
state_t *ptr;
|
||||
if (!block_head) {
|
||||
block_size *= 2;
|
||||
state_t *p = malloc(block_size * state_size);
|
||||
assert(p);
|
||||
p->next = block_root;
|
||||
block_root = p;
|
||||
ptr = (void*)((uint8_t*)p + state_size * block_size);
|
||||
p = block_head = next_of(p);
|
||||
state_t *q;
|
||||
for (q = next_of(p); q < ptr; p = q, q = next_of(q))
|
||||
p->next = q;
|
||||
p->next = NULL;
|
||||
}
|
||||
|
||||
ptr = block_head;
|
||||
block_head = block_head->next;
|
||||
|
||||
ptr->prev = parent;
|
||||
ptr->h = 0;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
inline
|
||||
void unnewstate(state_t *p) {
|
||||
p->next = block_head;
|
||||
block_head = p;
|
||||
}
|
||||
|
||||
enum { space, wall, player, box };
|
||||
|
||||
#define E "\033["
|
||||
const char * const glyph1[] = { " ", "#", E"31m@"E"m", E"33m$"E"m"};
|
||||
const char * const glyph2[] = { E"32m."E"m", "#", E"32m@"E"m", E"32m$"E"m"};
|
||||
#undef E
|
||||
|
||||
// mark up positions where a box definitely should not be
|
||||
void mark_live(const int c)
|
||||
{
|
||||
const int y = c / w, x = c % w;
|
||||
if (live[c]) return;
|
||||
|
||||
live[c] = 1;
|
||||
if (y > 1 && board[c - w] != wall && board[c - w * 2] != wall)
|
||||
mark_live(c - w);
|
||||
if (y < h - 2 && board[c + w] != wall && board[c + w * 2] != wall)
|
||||
mark_live(c + w);
|
||||
if (x > 1 && board[c - 1] != wall && board[c - 2] != wall)
|
||||
mark_live(c - 1);
|
||||
if (x < w - 2 && board[c + 1] != wall && board[c + 2] != wall)
|
||||
mark_live(c + 1);
|
||||
}
|
||||
|
||||
state_t *parse_board(const int y, const int x, const char *s)
|
||||
{
|
||||
w = x, h = y;
|
||||
board = calloc(w * h, sizeof(uint8_t));
|
||||
assert(board);
|
||||
goals = calloc(w * h, sizeof(uint8_t));
|
||||
assert(goals);
|
||||
live = calloc(w * h, sizeof(uint8_t));
|
||||
assert(live);
|
||||
|
||||
n_boxes = 0;
|
||||
for (int i = 0; s[i]; i++) {
|
||||
switch(s[i]) {
|
||||
case '#': board[i] = wall;
|
||||
continue;
|
||||
|
||||
case '.': // fallthrough
|
||||
case '+': goals[i] = 1; // fallthrough
|
||||
case '@': continue;
|
||||
|
||||
case '*': goals[i] = 1; // fallthrough
|
||||
case '$': n_boxes++;
|
||||
continue;
|
||||
default: continue;
|
||||
}
|
||||
}
|
||||
|
||||
const int is = sizeof(int);
|
||||
state_size = (sizeof(state_t) + (1 + n_boxes) * sizeof(cidx_t) + is - 1)
|
||||
/ is * is;
|
||||
|
||||
state_t *state = newstate(NULL);
|
||||
|
||||
for (int i = 0, j = 0; i < w * h; i++) {
|
||||
if (goals[i]) mark_live(i);
|
||||
if (s[i] == '$' || s[i] == '*')
|
||||
state->c[++j] = i;
|
||||
else if (s[i] == '@' || s[i] == '+')
|
||||
state->c[0] = i;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void show_board(const state_t *s)
|
||||
{
|
||||
unsigned char b[w * h];
|
||||
memcpy(b, board, w * h);
|
||||
|
||||
b[ s->c[0] ] = player;
|
||||
for (int i = 1; i <= n_boxes; i++)
|
||||
b[ s->c[i] ] = box;
|
||||
|
||||
for (int i = 0; i < w * h; i++) {
|
||||
printf((goals[i] ? glyph2 : glyph1)[ b[i] ]);
|
||||
if (! ((1 + i) % w))
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
|
||||
// K&R hash function
|
||||
inline
|
||||
void hash(state_t *s)
|
||||
{
|
||||
if (!s->h) {
|
||||
register hash_t ha = 0;
|
||||
cidx_t *p = s->c;
|
||||
for (int i = 0; i <= n_boxes; i++)
|
||||
ha = p[i] + 31 * ha;
|
||||
s->h = ha;
|
||||
}
|
||||
}
|
||||
|
||||
state_t **buckets;
|
||||
hash_t hash_size, fill_limit, filled;
|
||||
|
||||
void extend_table()
|
||||
{
|
||||
int old_size = hash_size;
|
||||
|
||||
if (!old_size) {
|
||||
hash_size = 1024;
|
||||
filled = 0;
|
||||
fill_limit = hash_size * 3 / 4; // 0.75 load factor
|
||||
} else {
|
||||
hash_size *= 2;
|
||||
fill_limit *= 2;
|
||||
}
|
||||
|
||||
buckets = realloc(buckets, sizeof(state_t*) * hash_size);
|
||||
assert(buckets);
|
||||
|
||||
// rehash
|
||||
memset(buckets + old_size, 0, sizeof(state_t*) * (hash_size - old_size));
|
||||
|
||||
const hash_t bits = hash_size - 1;
|
||||
for (int i = 0; i < old_size; i++) {
|
||||
state_t *head = buckets[i];
|
||||
buckets[i] = NULL;
|
||||
while (head) {
|
||||
state_t *next = head->next;
|
||||
const int j = head->h & bits;
|
||||
head->next = buckets[j];
|
||||
buckets[j] = head;
|
||||
head = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state_t *lookup(state_t *s)
|
||||
{
|
||||
hash(s);
|
||||
state_t *f = buckets[s->h & (hash_size - 1)];
|
||||
for (; f; f = f->next) {
|
||||
if (//(f->h == s->h) &&
|
||||
!memcmp(s->c, f->c, sizeof(cidx_t) * (1 + n_boxes)))
|
||||
break;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
bool add_to_table(state_t *s)
|
||||
{
|
||||
if (lookup(s)) {
|
||||
unnewstate(s);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filled++ >= fill_limit)
|
||||
extend_table();
|
||||
|
||||
hash_t i = s->h & (hash_size - 1);
|
||||
|
||||
s->next = buckets[i];
|
||||
buckets[i] = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool success(const state_t *s)
|
||||
{
|
||||
for (int i = 1; i <= n_boxes; i++)
|
||||
if (!goals[s->c[i]]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
state_t *move_me(state_t *s, const int dy, const int dx)
|
||||
{
|
||||
const int y = s->c[0] / w;
|
||||
const int x = s->c[0] % w;
|
||||
const int y1 = y + dy;
|
||||
const int x1 = x + dx;
|
||||
const int c1 = y1 * w + x1;
|
||||
|
||||
if (y1 < 0 || y1 > h || x1 < 0 || x1 > w
|
||||
|| board[c1] == wall)
|
||||
return NULL;
|
||||
|
||||
int at_box = 0;
|
||||
for (int i = 1; i <= n_boxes; i++) {
|
||||
if (s->c[i] == c1) {
|
||||
at_box = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int c2;
|
||||
if (at_box) {
|
||||
c2 = c1 + dy * w + dx;
|
||||
if (board[c2] == wall || !live[c2])
|
||||
return NULL;
|
||||
for (int i = 1; i <= n_boxes; i++)
|
||||
if (s->c[i] == c2) return NULL;
|
||||
}
|
||||
|
||||
state_t *n = newstate(s);
|
||||
memcpy(n->c + 1, s->c + 1, sizeof(cidx_t) * n_boxes);
|
||||
|
||||
cidx_t *p = n->c;
|
||||
p[0] = c1;
|
||||
|
||||
if (at_box) p[at_box] = c2;
|
||||
|
||||
// leet bubble sort
|
||||
for (int i = n_boxes; --i; ) {
|
||||
cidx_t t = 0;
|
||||
for (int j = 1; j < i; j++) {
|
||||
if (p[j] > p[j + 1])
|
||||
t = p[j], p[j] = p[j+1], p[j+1] = t;
|
||||
}
|
||||
if (!t) break;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
state_t *next_level, *done;
|
||||
|
||||
bool queue_move(state_t *s)
|
||||
{
|
||||
if (!s || !add_to_table(s))
|
||||
return false;
|
||||
|
||||
if (success(s)) {
|
||||
puts("\nSuccess!");
|
||||
done = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
s->qnext = next_level;
|
||||
next_level = s;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool do_move(state_t *s)
|
||||
{
|
||||
return queue_move(move_me(s, 1, 0))
|
||||
|| queue_move(move_me(s, -1, 0))
|
||||
|| queue_move(move_me(s, 0, 1))
|
||||
|| queue_move(move_me(s, 0, -1));
|
||||
}
|
||||
|
||||
void show_moves(const state_t *s)
|
||||
{
|
||||
if (s->prev)
|
||||
show_moves(s->prev);
|
||||
usleep(200000);
|
||||
printf("\033[H");
|
||||
show_board(s);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
state_t *s = parse_board(
|
||||
|
||||
#define BIG 0
|
||||
|
||||
#if BIG == 0
|
||||
8, 7,
|
||||
"#######"
|
||||
"# #"
|
||||
"# #"
|
||||
"#. # #"
|
||||
"#. $$ #"
|
||||
"#.$$ #"
|
||||
"#.# @#"
|
||||
"#######"
|
||||
|
||||
#elif BIG == 1
|
||||
5, 13,
|
||||
"#############"
|
||||
"# # #"
|
||||
"# $$$$$$$ @#"
|
||||
"#....... #"
|
||||
"#############"
|
||||
|
||||
#elif BIG == 2
|
||||
5, 13,
|
||||
"#############"
|
||||
"#... # #"
|
||||
"#.$$$$$$$ @#"
|
||||
"#... #"
|
||||
"#############"
|
||||
|
||||
#else
|
||||
11, 19,
|
||||
" ##### "
|
||||
" # # "
|
||||
" # # "
|
||||
" ### #$## "
|
||||
" # # "
|
||||
"### #$## # ######"
|
||||
"# # ## ##### .#"
|
||||
"# $ $ ..#"
|
||||
"##### ### #@## .#"
|
||||
" # #########"
|
||||
" ####### "
|
||||
#endif
|
||||
);
|
||||
|
||||
show_board(s);
|
||||
extend_table();
|
||||
queue_move(s);
|
||||
for (int i = 0; !done; i++) {
|
||||
printf("depth %d\r", i);
|
||||
fflush(stdout);
|
||||
|
||||
state_t *head = next_level;
|
||||
for (next_level = NULL; head && !done; head = head->qnext)
|
||||
do_move(head);
|
||||
|
||||
if (!next_level) {
|
||||
puts("no solution?");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
printf("press any key to see moves\n");
|
||||
getchar(), puts("\033[H\033[J");
|
||||
show_moves(done);
|
||||
|
||||
#if 0
|
||||
free(buckets);
|
||||
free(board);
|
||||
free(goals);
|
||||
free(live);
|
||||
|
||||
while (block_root) {
|
||||
void *tmp = block_root->next;
|
||||
free(block_root);
|
||||
block_root = tmp;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
145
Task/Sokoban/D/sokoban-1.d
Normal file
145
Task/Sokoban/D/sokoban-1.d
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import std.string, std.typecons, std.exception, std.algorithm;
|
||||
import queue_usage2; // No queue in Phobos 2.064.
|
||||
|
||||
const struct Board {
|
||||
private enum El { floor = ' ', wall = '#', goal = '.',
|
||||
box = '$', player = '@', boxOnGoal='*' }
|
||||
private alias CTable = string;
|
||||
private immutable size_t ncols;
|
||||
private immutable CTable sData, dData;
|
||||
private immutable int playerx, playery;
|
||||
|
||||
this(in string[] board) immutable pure nothrow @safe
|
||||
in {
|
||||
foreach (const row; board) {
|
||||
assert(row.length == board[0].length,
|
||||
"Unequal board rows.");
|
||||
foreach (immutable c; row)
|
||||
assert(c.inPattern(" #.$@*"), "Not valid input");
|
||||
}
|
||||
} body {
|
||||
/*static*/ immutable sMap =
|
||||
[' ':' ', '.':'.', '@':' ', '#':'#', '$':' '];
|
||||
/*static*/ immutable dMap =
|
||||
[' ':' ', '.':' ', '@':'@', '#':' ', '$':'*'];
|
||||
ncols = board[0].length;
|
||||
|
||||
int plx = 0, ply = 0;
|
||||
CTable sDataBuild, dDataBuild;
|
||||
|
||||
foreach (immutable r, const row; board)
|
||||
foreach (immutable c, const ch; row) {
|
||||
sDataBuild ~= sMap[ch];
|
||||
dDataBuild ~= dMap[ch];
|
||||
if (ch == El.player) {
|
||||
plx = c;
|
||||
ply = r;
|
||||
}
|
||||
}
|
||||
|
||||
this.sData = sDataBuild;
|
||||
this.dData = dDataBuild;
|
||||
this.playerx = plx;
|
||||
this.playery = ply;
|
||||
}
|
||||
|
||||
private bool move(in int x, in int y, in int dx,
|
||||
in int dy, ref CTable data)
|
||||
const pure nothrow /*@safe*/ {
|
||||
if (sData[(y + dy) * ncols + x + dx] == El.wall ||
|
||||
data[(y + dy) * ncols + x + dx] != El.floor)
|
||||
return false;
|
||||
|
||||
auto data2 = data.dup;
|
||||
data2[y * ncols + x] = El.floor;
|
||||
data2[(y + dy) * ncols + x + dx] = El.player;
|
||||
data = data2.assumeUnique; // Not enforced.
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool push(in int x, in int y, in int dx,
|
||||
in int dy, ref CTable data)
|
||||
const pure nothrow /*@safe*/ {
|
||||
if (sData[(y + 2 * dy) * ncols + x + 2 * dx] == El.wall ||
|
||||
data[(y + 2 * dy) * ncols + x + 2 * dx] != El.floor)
|
||||
return false;
|
||||
|
||||
auto data2 = data.dup;
|
||||
data2[y * ncols + x] = El.floor;
|
||||
data2[(y + dy) * ncols + x + dx] = El.player;
|
||||
data2[(y + 2 * dy) * ncols + x + 2*dx] = El.boxOnGoal;
|
||||
data = data2.assumeUnique; // Not enforced.
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool isSolved(in CTable data)
|
||||
const pure nothrow @safe @nogc {
|
||||
foreach (immutable i, immutable d; data)
|
||||
if ((sData[i] == El.goal) != (d == El.boxOnGoal))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
string solve() pure nothrow /*@safe*/ {
|
||||
bool[immutable CTable] visitedSet = [dData: true];
|
||||
|
||||
alias Four = Tuple!(CTable, string, int, int);
|
||||
GrowableCircularQueue!Four open;
|
||||
open.push(Four(dData, "", playerx, playery));
|
||||
|
||||
static immutable dirs = [tuple( 0, -1, 'u', 'U'),
|
||||
tuple( 1, 0, 'r', 'R'),
|
||||
tuple( 0, 1, 'd', 'D'),
|
||||
tuple(-1, 0, 'l', 'L')];
|
||||
|
||||
while (!open.empty) {
|
||||
//immutable (cur, cSol, x, y) = open.pop;
|
||||
immutable item = open.pop;
|
||||
immutable cur = item[0];
|
||||
immutable cSol = item[1];
|
||||
immutable x = item[2];
|
||||
immutable y = item[3];
|
||||
|
||||
foreach (immutable di; dirs) {
|
||||
CTable temp = cur;
|
||||
//immutable (dx, dy) = di[0 .. 2];
|
||||
immutable dx = di[0];
|
||||
immutable dy = di[1];
|
||||
|
||||
if (temp[(y + dy) * ncols + x + dx] == El.boxOnGoal) {
|
||||
if (push(x, y, dx, dy, temp) && temp !in visitedSet) {
|
||||
if (isSolved(temp))
|
||||
return cSol ~ di[3];
|
||||
open.push(Four(temp, cSol ~ di[3], x + dx, y + dy));
|
||||
visitedSet[temp] = true;
|
||||
}
|
||||
} else if (move(x, y, dx, dy, temp) && temp !in visitedSet) {
|
||||
if (isSolved(temp))
|
||||
return cSol ~ di[2];
|
||||
open.push(Four(temp, cSol ~ di[2], x + dx, y + dy));
|
||||
visitedSet[temp] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "No solution";
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
import std.stdio, core.memory;
|
||||
GC.disable; // Uses about twice the memory.
|
||||
|
||||
immutable level =
|
||||
"#######
|
||||
# #
|
||||
# #
|
||||
#. # #
|
||||
#. $$ #
|
||||
#.$$ #
|
||||
#.# @#
|
||||
#######";
|
||||
|
||||
immutable b = immutable(Board)(level.splitLines);
|
||||
writeln(level, "\n\n", b.solve);
|
||||
}
|
||||
414
Task/Sokoban/D/sokoban-2.d
Normal file
414
Task/Sokoban/D/sokoban-2.d
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
import core.stdc.stdio: printf, puts, fflush, stdout, putchar;
|
||||
import core.stdc.stdlib: malloc, calloc, realloc, free, alloca, exit;
|
||||
|
||||
enum Cell : ubyte { space, wall, player, box }
|
||||
alias CellIndex = ushort;
|
||||
alias Thash = uint;
|
||||
|
||||
|
||||
/// Board configuration is represented by an array of cell
|
||||
/// indices of player and boxes.
|
||||
struct State { // Variable length struct.
|
||||
Thash h;
|
||||
State* prev, next, qNext;
|
||||
CellIndex[0] c_;
|
||||
|
||||
CellIndex get(in size_t i) inout pure nothrow @nogc {
|
||||
return c_.ptr[i];
|
||||
}
|
||||
|
||||
void set(in size_t i, in CellIndex v) pure nothrow @nogc {
|
||||
c_.ptr[i] = v;
|
||||
}
|
||||
|
||||
CellIndex[] slice(in size_t i, in size_t j) pure nothrow @nogc return {
|
||||
return c_.ptr[i .. j];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
__gshared Cell[] board;
|
||||
__gshared bool[] goals, live;
|
||||
__gshared size_t w, h, nBoxes, stateSize, blockSize = 32;
|
||||
__gshared State* blockRoot, blockHead, nextLevel, done;
|
||||
__gshared State*[] buckets;
|
||||
__gshared Thash hashSize, fillLimit, filled;
|
||||
|
||||
|
||||
State* newState(State* parent) nothrow @nogc {
|
||||
static State* nextOf(State *s) nothrow @nogc {
|
||||
return cast(State*)(cast(ubyte*)s + stateSize);
|
||||
}
|
||||
|
||||
State* ptr;
|
||||
if (!blockHead) {
|
||||
blockSize *= 2;
|
||||
auto p = cast(State*)malloc(blockSize * stateSize);
|
||||
if (p == null)
|
||||
exit(1);
|
||||
|
||||
p.next = blockRoot;
|
||||
blockRoot = p;
|
||||
ptr = cast(State*)(cast(ubyte*)p + stateSize * blockSize);
|
||||
p = blockHead = nextOf(p);
|
||||
for (auto q = nextOf(p); q < ptr; p = q, q = nextOf(q))
|
||||
p.next = q;
|
||||
p.next = null;
|
||||
}
|
||||
|
||||
ptr = blockHead;
|
||||
blockHead = blockHead.next;
|
||||
ptr.prev = parent;
|
||||
ptr.h = 0;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
|
||||
void unNewState(State* p) nothrow @nogc {
|
||||
p.next = blockHead;
|
||||
blockHead = p;
|
||||
}
|
||||
|
||||
|
||||
/// Mark up positions where a box definitely should not be.
|
||||
void markLive(in size_t c) nothrow @nogc {
|
||||
immutable y = c / w;
|
||||
immutable x = c % w;
|
||||
if (live[c])
|
||||
return;
|
||||
|
||||
live[c] = true;
|
||||
if (y > 1 && board[c - w] != Cell.wall &&
|
||||
board[c - w * 2] != Cell.wall)
|
||||
markLive(c - w);
|
||||
if (y < h - 2 && board[c + w] != Cell.wall &&
|
||||
board[c + w * 2] != Cell.wall)
|
||||
markLive(c + w);
|
||||
if (x > 1 && board[c - 1] != Cell.wall &&
|
||||
board[c - 2] != Cell.wall)
|
||||
markLive(c - 1);
|
||||
if (x < w - 2 && board[c + 1] != Cell.wall &&
|
||||
board[c + 2] != Cell.wall)
|
||||
markLive(c + 1);
|
||||
}
|
||||
|
||||
|
||||
State* parseBoard(in size_t y, in size_t x, in char* s) nothrow @nogc {
|
||||
static T[] myCalloc(T)(in size_t n) nothrow @nogc {
|
||||
auto ptr = cast(T*)calloc(n, T.sizeof);
|
||||
if (ptr == null)
|
||||
exit(1);
|
||||
return ptr[0 .. n];
|
||||
}
|
||||
|
||||
w = x, h = y;
|
||||
board = myCalloc!Cell(w * h);
|
||||
goals = myCalloc!bool(w * h);
|
||||
live = myCalloc!bool(w * h);
|
||||
|
||||
nBoxes = 0;
|
||||
for (int i = 0; s[i]; i++) {
|
||||
switch(s[i]) {
|
||||
case '#':
|
||||
board[i] = Cell.wall;
|
||||
continue;
|
||||
case '.', '+':
|
||||
goals[i] = true;
|
||||
goto case;
|
||||
case '@':
|
||||
continue;
|
||||
case '*':
|
||||
goals[i] = true;
|
||||
goto case;
|
||||
case '$':
|
||||
nBoxes++;
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
enum int intSize = int.sizeof;
|
||||
stateSize = (State.sizeof +
|
||||
(1 + nBoxes) * CellIndex.sizeof +
|
||||
intSize - 1)
|
||||
/ intSize * intSize;
|
||||
|
||||
auto state = null.newState;
|
||||
|
||||
for (CellIndex i = 0, j = 0; i < w * h; i++) {
|
||||
if (goals[i])
|
||||
i.markLive;
|
||||
if (s[i] == '$' || s[i] == '*')
|
||||
state.set(++j, i);
|
||||
else if (s[i] == '@' || s[i] == '+')
|
||||
state.set(0, i);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
/// K&R hash function.
|
||||
void hash(State* s, in size_t nBoxes) pure nothrow @nogc {
|
||||
if (!s.h) {
|
||||
Thash ha = 0;
|
||||
foreach (immutable i; 0 .. nBoxes + 1)
|
||||
ha = s.get(i) + 31 * ha;
|
||||
s.h = ha;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void extendTable() nothrow @nogc {
|
||||
int oldSize = hashSize;
|
||||
|
||||
if (!oldSize) {
|
||||
hashSize = 1024;
|
||||
filled = 0;
|
||||
fillLimit = hashSize * 3 / 4; // 0.75 load factor.
|
||||
} else {
|
||||
hashSize *= 2;
|
||||
fillLimit *= 2;
|
||||
}
|
||||
|
||||
auto ptr = cast(State**)realloc(buckets.ptr,
|
||||
(State*).sizeof * hashSize);
|
||||
if (ptr == null)
|
||||
exit(6);
|
||||
buckets = ptr[0 .. hashSize];
|
||||
buckets[oldSize .. hashSize] = null;
|
||||
|
||||
immutable Thash bits = hashSize - 1;
|
||||
foreach (immutable i; 0 .. oldSize) {
|
||||
auto head = buckets[i];
|
||||
buckets[i] = null;
|
||||
while (head) {
|
||||
auto next = head.next;
|
||||
immutable j = head.h & bits;
|
||||
head.next = buckets[j];
|
||||
buckets[j] = head;
|
||||
head = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
State* lookup(State *s) nothrow @nogc {
|
||||
s.hash(nBoxes);
|
||||
auto f = buckets[s.h & (hashSize - 1)];
|
||||
for (; f; f = f.next) {
|
||||
if (s.slice(0, nBoxes + 1) == f.slice(0, nBoxes + 1))
|
||||
break;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
bool addToTable(State* s) nothrow @nogc {
|
||||
if (s.lookup) {
|
||||
s.unNewState;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filled++ >= fillLimit)
|
||||
extendTable;
|
||||
|
||||
immutable Thash i = s.h & (hashSize - 1);
|
||||
|
||||
s.next = buckets[i];
|
||||
buckets[i] = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool success(in State* s) nothrow @nogc {
|
||||
foreach (immutable i; 1 .. nBoxes + 1)
|
||||
if (!goals[s.get(i)])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
State* moveMe(State* s, in int dy, in int dx) nothrow @nogc {
|
||||
immutable int y = s.get(0) / w;
|
||||
immutable int x = s.get(0) % w;
|
||||
immutable int y1 = y + dy;
|
||||
immutable int x1 = x + dx;
|
||||
immutable int c1 = y1 * w + x1;
|
||||
|
||||
if (y1 < 0 || y1 > h || x1 < 0 || x1 > w || board[c1] == Cell.wall)
|
||||
return null;
|
||||
|
||||
int atBox = 0;
|
||||
foreach (immutable i; 1 .. nBoxes + 1)
|
||||
if (s.get(i) == c1) {
|
||||
atBox = i;
|
||||
break;
|
||||
}
|
||||
|
||||
int c2;
|
||||
if (atBox) {
|
||||
c2 = c1 + dy * w + dx;
|
||||
if (board[c2] == Cell.wall || !live[c2])
|
||||
return null;
|
||||
foreach (immutable i; 1 .. nBoxes + 1)
|
||||
if (s.get(i) == c2)
|
||||
return null;
|
||||
}
|
||||
|
||||
auto n = s.newState;
|
||||
n.slice(1, nBoxes + 1)[] = s.slice(1, nBoxes + 1);
|
||||
|
||||
n.set(0, cast(CellIndex)c1);
|
||||
|
||||
if (atBox)
|
||||
n.set(atBox, cast(CellIndex)c2);
|
||||
|
||||
// Bubble sort.
|
||||
for (size_t i = nBoxes; --i; ) {
|
||||
CellIndex t = 0;
|
||||
foreach (immutable j; 1 .. i) {
|
||||
if (n.get(j) > n.get(j + 1)) {
|
||||
t = n.get(j);
|
||||
n.set(j, n.get(j + 1));
|
||||
n.set(j + 1, t);
|
||||
}
|
||||
}
|
||||
if (!t)
|
||||
break;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
bool queueMove(State *s) nothrow @nogc {
|
||||
if (!s || !s.addToTable)
|
||||
return false;
|
||||
|
||||
if (s.success) {
|
||||
"\nSuccess!".puts;
|
||||
done = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
s.qNext = nextLevel;
|
||||
nextLevel = s;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool doMove(State* s) nothrow @nogc {
|
||||
return s.moveMe( 1, 0).queueMove ||
|
||||
s.moveMe(-1, 0).queueMove ||
|
||||
s.moveMe( 0, 1).queueMove ||
|
||||
s.moveMe( 0, -1).queueMove;
|
||||
}
|
||||
|
||||
|
||||
void showBoard(in State* s) nothrow @nogc {
|
||||
static immutable glyphs1 = " #@$", glyphs2 = ".#@$";
|
||||
|
||||
auto ptr = cast(ubyte*)alloca(w * h * ubyte.sizeof);
|
||||
if (ptr == null)
|
||||
exit(5);
|
||||
auto b = ptr[0 .. w * h];
|
||||
b[] = cast(typeof(b))board[];
|
||||
|
||||
b[s.get(0)] = Cell.player;
|
||||
foreach (immutable i; 1 .. nBoxes + 1)
|
||||
b[s.get(i)] = Cell.box;
|
||||
|
||||
foreach (immutable i, immutable bi; b) {
|
||||
putchar((goals[i] ? glyphs2 : glyphs1)[bi]);
|
||||
if (!((1 + i) % w))
|
||||
'\n'.putchar;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void showMoves(in State* s) nothrow @nogc {
|
||||
if (s.prev)
|
||||
s.prev.showMoves;
|
||||
"\n".printf;
|
||||
s.showBoard;
|
||||
}
|
||||
|
||||
int main() nothrow @nogc {
|
||||
// Workaround for @nogc.
|
||||
alias ctEval(alias expr) = expr;
|
||||
|
||||
enum uint problem = 0;
|
||||
|
||||
static if (problem == 0) {
|
||||
auto s = parseBoard(8, 7, ctEval!(
|
||||
"#######"~
|
||||
"# #"~
|
||||
"# #"~
|
||||
"#. # #"~
|
||||
"#. $$ #"~
|
||||
"#.$$ #"~
|
||||
"#.# @#"~
|
||||
"#######"));
|
||||
|
||||
} else static if (problem == 1) {
|
||||
auto s = parseBoard(5, 13, ctEval!(
|
||||
"#############"~
|
||||
"# # #"~
|
||||
"# $$$$$$$ @#"~
|
||||
"#....... #"
|
||||
"#############"));
|
||||
|
||||
} else static if (problem == 2) {
|
||||
auto s = parseBoard(11, 19, ctEval!(
|
||||
" ##### "~
|
||||
" # # "~
|
||||
" # # "~
|
||||
" ### #$## "~
|
||||
" # # "~
|
||||
"### #$## # ######"~
|
||||
"# # ## ##### .#"~
|
||||
"# $ $ ..#"~
|
||||
"##### ### #@## .#"~
|
||||
" # #########"~
|
||||
" ####### "));
|
||||
} else {
|
||||
asset(0, "Not present problem.");
|
||||
}
|
||||
|
||||
s.showBoard;
|
||||
extendTable;
|
||||
s.queueMove;
|
||||
for (int i = 0; !done; i++) {
|
||||
printf("depth %d\r", i);
|
||||
stdout.fflush;
|
||||
|
||||
auto head = nextLevel;
|
||||
for (nextLevel = null; head && !done; head = head.qNext)
|
||||
head.doMove;
|
||||
|
||||
if (!nextLevel) {
|
||||
"No solution?".puts;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
done.showMoves;
|
||||
|
||||
version (none) { // Free all allocated memory.
|
||||
buckets.ptr.free;
|
||||
board.ptr.free;
|
||||
goals.ptr.free;
|
||||
live.ptr.free;
|
||||
|
||||
while (blockRoot) {
|
||||
auto tmp = blockRoot.next;
|
||||
blockRoot.free;
|
||||
blockRoot = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
181
Task/Sokoban/Elixir/sokoban.elixir
Normal file
181
Task/Sokoban/Elixir/sokoban.elixir
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
defmodule Sokoban do
|
||||
defp setup(level) do
|
||||
{leng, board} = normalize(level)
|
||||
{player, goal} = check_position(board)
|
||||
board = replace(board, [{".", " "}, {"+", " "}, {"*", "$"}])
|
||||
lurd = [{-1, "l", "L"}, {-leng, "u", "U"}, {1, "r", "R"}, {leng, "d", "D"}]
|
||||
dirs = [-1, -leng, 1, leng]
|
||||
dead_zone = set_dead_zone(board, goal, dirs)
|
||||
{board, player, goal, lurd, dead_zone}
|
||||
end
|
||||
|
||||
defp normalize(level) do
|
||||
board = String.split(level, "\n", trim: true)
|
||||
|> Enum.map(&String.trim_trailing &1)
|
||||
leng = Enum.map(board, &String.length &1) |> Enum.max
|
||||
board = Enum.map(board, &String.pad_trailing(&1, leng)) |> Enum.join
|
||||
{leng, board}
|
||||
end
|
||||
|
||||
defp check_position(board) do
|
||||
board = String.codepoints(board)
|
||||
player = Enum.find_index(board, fn c -> c in ["@", "+"] end)
|
||||
goal = Enum.with_index(board)
|
||||
|> Enum.filter_map(fn {c,_} -> c in [".", "+", "*"] end, fn {_,i} -> i end)
|
||||
{player, goal}
|
||||
end
|
||||
|
||||
defp set_dead_zone(board, goal, dirs) do
|
||||
wall = String.replace(board, ~r/[^#]/, " ")
|
||||
|> String.codepoints
|
||||
|> Enum.with_index
|
||||
|> Enum.into(Map.new, fn {c,i} -> {i,c} end)
|
||||
corner = search_corner(wall, goal, dirs)
|
||||
set_dead_zone(wall, dirs, goal, corner, corner)
|
||||
end
|
||||
|
||||
defp set_dead_zone(wall, dirs, goal, corner, dead) do
|
||||
dead2 = Enum.reduce(corner, dead, fn pos,acc ->
|
||||
Enum.reduce(dirs, acc, fn dir,acc2 ->
|
||||
if wall[pos+dir] == "#", do: acc2,
|
||||
else: acc2 ++ check_side(wall, dirs, pos+dir, dir, goal, dead, [])
|
||||
end)
|
||||
end)
|
||||
if dead == dead2, do: :lists.usort(dead),
|
||||
else: set_dead_zone(wall, dirs, goal, corner, dead2)
|
||||
end
|
||||
|
||||
defp replace(string, replacement) do
|
||||
Enum.reduce(replacement, string, fn {a,b},str ->
|
||||
String.replace(str, a, b)
|
||||
end)
|
||||
end
|
||||
|
||||
defp search_corner(wall, goal, dirs) do
|
||||
Enum.reduce(wall, [], fn {i,c},corner ->
|
||||
if c == "#" or i in goal do
|
||||
corner
|
||||
else
|
||||
case count_wall(wall, i, dirs) do
|
||||
2 -> if wall[i-1] != wall[i+1], do: [i | corner], else: corner
|
||||
3 -> [i | corner]
|
||||
_ -> corner
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp check_side(wall, dirs, pos, dir, goal, dead, acc) do
|
||||
if wall[pos] == "#" or
|
||||
count_wall(wall, pos, dirs) == 0 or
|
||||
pos in goal do
|
||||
[]
|
||||
else
|
||||
if pos in dead, do: acc, else: check_side(wall, dirs, pos+dir, dir, goal, dead, [pos|acc])
|
||||
end
|
||||
end
|
||||
|
||||
defp count_wall(wall, pos, dirs) do
|
||||
Enum.count(dirs, fn dir -> wall[pos + dir] == "#" end)
|
||||
end
|
||||
|
||||
defp push_box(board, pos, dir, route, goal, dead_zone) do
|
||||
pos2dir = pos + 2 * dir
|
||||
if String.at(board, pos2dir) == " " and not pos2dir in dead_zone do
|
||||
board2 = board |> replace_at(pos, " ")
|
||||
|> replace_at(pos+dir, "@")
|
||||
|> replace_at(pos2dir, "$")
|
||||
unless visited?(board2) do
|
||||
if solved?(board2, goal) do
|
||||
IO.puts route
|
||||
exit(:normal)
|
||||
else
|
||||
queue_in({board2, pos+dir, route})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp move_player(board, pos, dir) do
|
||||
board |> replace_at(pos, " ") |> replace_at(pos+dir, "@")
|
||||
end
|
||||
|
||||
defp replace_at(str, pos, c) do
|
||||
{left, right} = String.split_at(str, pos)
|
||||
{_, right} = String.split_at(right, 1)
|
||||
left <> c <> right
|
||||
# String.slice(str, 0, pos) <> c <> String.slice(str, pos+1..-1)
|
||||
end
|
||||
|
||||
defp solved?(board, goal) do
|
||||
Enum.all?(goal, fn g -> String.at(board, g) == "$" end)
|
||||
end
|
||||
|
||||
@pattern :sokoban_pattern_set
|
||||
@queue :sokoban_queue
|
||||
|
||||
defp start_link do
|
||||
Agent.start_link(fn -> MapSet.new end, name: @pattern)
|
||||
Agent.start_link(fn -> :queue.new end, name: @queue)
|
||||
end
|
||||
|
||||
defp visited?(board) do
|
||||
Agent.get_and_update(@pattern, fn set ->
|
||||
{board in set, MapSet.put(set, board)}
|
||||
end)
|
||||
end
|
||||
|
||||
defp queue_in(data) do
|
||||
Agent.update(@queue, fn queue -> :queue.in(data, queue) end)
|
||||
end
|
||||
|
||||
defp queue_out do
|
||||
Agent.get_and_update(@queue, fn q ->
|
||||
case :queue.out(q) do
|
||||
{{:value, data}, queue} -> {data, queue}
|
||||
x -> x
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def solve(level) do
|
||||
{board, player, goal, lurd, dead_zone} = setup(level)
|
||||
start_link
|
||||
visited?(board)
|
||||
queue_in({board, player, ""})
|
||||
solve(goal, lurd, dead_zone)
|
||||
end
|
||||
|
||||
defp solve(goal, lurd, dead_zone) do
|
||||
case queue_out do
|
||||
{board, pos, route} ->
|
||||
Enum.each(lurd, fn {dir,move,push} ->
|
||||
case String.at(board, pos+dir) do
|
||||
"$" -> push_box(board, pos, dir, route<>push, goal, dead_zone)
|
||||
" " -> board2 = move_player(board, pos, dir)
|
||||
unless visited?(board2) do
|
||||
queue_in({board2, pos+dir, route<>move})
|
||||
end
|
||||
_ -> :not_move # wall
|
||||
end
|
||||
end)
|
||||
_ ->
|
||||
IO.puts "No solution"
|
||||
exit(:normal)
|
||||
end
|
||||
solve(goal, lurd, dead_zone)
|
||||
end
|
||||
end
|
||||
|
||||
level = """
|
||||
#######
|
||||
# #
|
||||
# #
|
||||
#. # #
|
||||
#. $$ #
|
||||
#.$$ #
|
||||
#.# @#
|
||||
#######
|
||||
"""
|
||||
IO.puts level
|
||||
Sokoban.solve(level)
|
||||
117
Task/Sokoban/Go/sokoban.go
Normal file
117
Task/Sokoban/Go/sokoban.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
level := `
|
||||
#######
|
||||
# #
|
||||
# #
|
||||
#. # #
|
||||
#. $$ #
|
||||
#.$$ #
|
||||
#.# @#
|
||||
#######`
|
||||
fmt.Printf("level:%s\n", level)
|
||||
fmt.Printf("solution:\n%s\n", solve(level))
|
||||
}
|
||||
|
||||
func solve(board string) string {
|
||||
buffer = make([]byte, len(board))
|
||||
width := strings.Index(board[1:], "\n") + 1
|
||||
dirs := []struct {
|
||||
move, push string
|
||||
dPos int
|
||||
}{
|
||||
{"u", "U", -width},
|
||||
{"r", "R", 1},
|
||||
{"d", "D", width},
|
||||
{"l", "L", -1},
|
||||
}
|
||||
visited := map[string]bool{board: true}
|
||||
open := []state{state{board, "", strings.Index(board, "@")}}
|
||||
for len(open) > 0 {
|
||||
s1 := &open[0]
|
||||
open = open[1:]
|
||||
for _, dir := range dirs {
|
||||
var newBoard, newSol string
|
||||
newPos := s1.pos + dir.dPos
|
||||
switch s1.board[newPos] {
|
||||
case '$', '*':
|
||||
newBoard = s1.push(dir.dPos)
|
||||
if newBoard == "" || visited[newBoard] {
|
||||
continue
|
||||
}
|
||||
newSol = s1.cSol + dir.push
|
||||
if strings.IndexAny(newBoard, ".+") < 0 {
|
||||
return newSol
|
||||
}
|
||||
case ' ', '.':
|
||||
newBoard = s1.move(dir.dPos)
|
||||
if visited[newBoard] {
|
||||
continue
|
||||
}
|
||||
newSol = s1.cSol + dir.move
|
||||
default:
|
||||
continue
|
||||
}
|
||||
open = append(open, state{newBoard, newSol, newPos})
|
||||
visited[newBoard] = true
|
||||
}
|
||||
}
|
||||
return "No solution"
|
||||
}
|
||||
|
||||
type state struct {
|
||||
board string
|
||||
cSol string
|
||||
pos int
|
||||
}
|
||||
|
||||
var buffer []byte
|
||||
|
||||
func (s *state) move(dPos int) string {
|
||||
copy(buffer, s.board)
|
||||
if buffer[s.pos] == '@' {
|
||||
buffer[s.pos] = ' '
|
||||
} else {
|
||||
buffer[s.pos] = '.'
|
||||
}
|
||||
newPos := s.pos + dPos
|
||||
if buffer[newPos] == ' ' {
|
||||
buffer[newPos] = '@'
|
||||
} else {
|
||||
buffer[newPos] = '+'
|
||||
}
|
||||
return string(buffer)
|
||||
}
|
||||
|
||||
func (s *state) push(dPos int) string {
|
||||
newPos := s.pos + dPos
|
||||
boxPos := newPos + dPos
|
||||
switch s.board[boxPos] {
|
||||
case ' ', '.':
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
copy(buffer, s.board)
|
||||
if buffer[s.pos] == '@' {
|
||||
buffer[s.pos] = ' '
|
||||
} else {
|
||||
buffer[s.pos] = '.'
|
||||
}
|
||||
if buffer[newPos] == '$' {
|
||||
buffer[newPos] = '@'
|
||||
} else {
|
||||
buffer[newPos] = '+'
|
||||
}
|
||||
if buffer[boxPos] == ' ' {
|
||||
buffer[boxPos] = '$'
|
||||
} else {
|
||||
buffer[boxPos] = '*'
|
||||
}
|
||||
return string(buffer)
|
||||
}
|
||||
132
Task/Sokoban/Haskell/sokoban.hs
Normal file
132
Task/Sokoban/Haskell/sokoban.hs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import Control.Monad (liftM)
|
||||
import Data.Array
|
||||
import Data.List (transpose)
|
||||
import Data.Maybe (mapMaybe)
|
||||
import qualified Data.Sequence as Seq
|
||||
import qualified Data.Set as Set
|
||||
import Prelude hiding (Left, Right)
|
||||
|
||||
data Field = Space | Wall | Goal
|
||||
deriving (Eq)
|
||||
|
||||
data Action = Up | Down | Left | Right | PushUp | PushDown | PushLeft | PushRight
|
||||
|
||||
instance Show Action where
|
||||
show Up = "u"
|
||||
show Down = "d"
|
||||
show Left = "l"
|
||||
show Right = "r"
|
||||
show PushUp = "U"
|
||||
show PushDown = "D"
|
||||
show PushLeft = "L"
|
||||
show PushRight = "R"
|
||||
|
||||
type Index = (Int, Int)
|
||||
type FieldArray = Array Index Field
|
||||
type BoxArray = Array Index Bool
|
||||
type PlayerPos = Index
|
||||
type GameState = (BoxArray, PlayerPos)
|
||||
type Game = (FieldArray, GameState)
|
||||
|
||||
toField :: Char -> Field
|
||||
toField '#' = Wall
|
||||
toField ' ' = Space
|
||||
toField '@' = Space
|
||||
toField '$' = Space
|
||||
toField '.' = Goal
|
||||
toField '+' = Goal
|
||||
toField '*' = Goal
|
||||
|
||||
toPush :: Action -> Action
|
||||
toPush Up = PushUp
|
||||
toPush Down = PushDown
|
||||
toPush Left = PushLeft
|
||||
toPush Right = PushRight
|
||||
toPush n = n
|
||||
|
||||
toMove :: Action -> Index
|
||||
toMove PushUp = ( 0, -1)
|
||||
toMove PushDown = ( 0, 1)
|
||||
toMove PushLeft = (-1, 0)
|
||||
toMove PushRight = ( 1, 0)
|
||||
toMove n = toMove $ toPush n
|
||||
|
||||
-- Parse the string-based game board into an easier-to-use format.
|
||||
-- Assume that the board is valid (rectangular, one player, etc).
|
||||
parseGame :: [String] -> Game
|
||||
parseGame fieldStrs = (field, (boxes, player))
|
||||
where
|
||||
width = length $ head fieldStrs
|
||||
height = length fieldStrs
|
||||
bound = ((0, 0), (width - 1, height - 1))
|
||||
flatField = concat $ transpose fieldStrs
|
||||
charField = listArray bound flatField
|
||||
field = fmap toField charField
|
||||
boxes = fmap (`elem` "$*") charField
|
||||
player = fst $ head $ filter (flip elem "@+" . snd) $ assocs charField
|
||||
|
||||
add :: (Num a, Num b) => (a, b) -> (a, b) -> (a, b)
|
||||
add (a, b) (x, y) = (a + x, b + y)
|
||||
|
||||
-- Attempt to perform an action, returning the updated game and adjusted
|
||||
-- action if the action was legal.
|
||||
tryAction :: Game -> Action -> Maybe (Game, Action)
|
||||
tryAction (field, (boxes, player)) action
|
||||
| field ! vec == Wall = Nothing
|
||||
| boxes ! vec =
|
||||
if boxes ! vecB || field ! vecB == Wall
|
||||
then Nothing
|
||||
else Just ((field, (boxes // [(vec, False), (vecB, True)], vec)),
|
||||
toPush action)
|
||||
| otherwise = Just ((field, (boxes, vec)), action)
|
||||
where
|
||||
actionVec = toMove action
|
||||
vec = player `add` actionVec
|
||||
vecB = vec `add` actionVec
|
||||
|
||||
-- Search the game for a solution.
|
||||
solveGame :: Game -> Maybe [Action]
|
||||
solveGame (field, initState) =
|
||||
liftM reverse $ bfs (Seq.singleton (initState, [])) (Set.singleton initState)
|
||||
where
|
||||
goals = map fst $ filter ((== Goal) . snd) $ assocs field
|
||||
isSolved st = all (st !) goals
|
||||
possibleActions = [Up, Down, Left, Right]
|
||||
|
||||
-- Breadth First Search of the game tree.
|
||||
bfs :: Seq.Seq (GameState, [Action]) -> Set.Set GameState -> Maybe [Action]
|
||||
bfs queue visited =
|
||||
case Seq.viewl queue of
|
||||
Seq.EmptyL -> Nothing
|
||||
(game@(boxes, _), actions) Seq.:< queueB ->
|
||||
if isSolved boxes
|
||||
then Just actions
|
||||
else
|
||||
let newMoves = filter (flip Set.notMember visited . fst) $
|
||||
map (\((_, g), a) -> (g, a)) $
|
||||
mapMaybe (tryAction (field, game)) possibleActions
|
||||
visitedB = foldl (flip Set.insert) visited $
|
||||
map fst newMoves
|
||||
queueC = foldl (Seq.|>) queueB $
|
||||
map (\(g, a) -> (g, a:actions)) newMoves
|
||||
in bfs queueC visitedB
|
||||
|
||||
exampleA :: [String]
|
||||
exampleA =
|
||||
["#######"
|
||||
,"# #"
|
||||
,"# #"
|
||||
,"#. # #"
|
||||
,"#. $$ #"
|
||||
,"#.$$ #"
|
||||
,"#.# @#"
|
||||
,"#######"]
|
||||
|
||||
main :: IO ()
|
||||
main =
|
||||
case solveGame $ parseGame exampleA of
|
||||
Nothing -> putStrLn "Unsolvable"
|
||||
Just solution -> do
|
||||
mapM_ putStrLn exampleA
|
||||
putStrLn ""
|
||||
putStrLn $ concatMap show solution
|
||||
138
Task/Sokoban/Java/sokoban.java
Normal file
138
Task/Sokoban/Java/sokoban.java
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import java.util.*;
|
||||
|
||||
public class Sokoban {
|
||||
String destBoard, currBoard;
|
||||
int playerX, playerY, nCols;
|
||||
|
||||
Sokoban(String[] board) {
|
||||
nCols = board[0].length();
|
||||
StringBuilder destBuf = new StringBuilder();
|
||||
StringBuilder currBuf = new StringBuilder();
|
||||
|
||||
for (int r = 0; r < board.length; r++) {
|
||||
for (int c = 0; c < nCols; c++) {
|
||||
|
||||
char ch = board[r].charAt(c);
|
||||
|
||||
destBuf.append(ch != '$' && ch != '@' ? ch : ' ');
|
||||
currBuf.append(ch != '.' ? ch : ' ');
|
||||
|
||||
if (ch == '@') {
|
||||
this.playerX = c;
|
||||
this.playerY = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
destBoard = destBuf.toString();
|
||||
currBoard = currBuf.toString();
|
||||
}
|
||||
|
||||
String move(int x, int y, int dx, int dy, String trialBoard) {
|
||||
|
||||
int newPlayerPos = (y + dy) * nCols + x + dx;
|
||||
|
||||
if (trialBoard.charAt(newPlayerPos) != ' ')
|
||||
return null;
|
||||
|
||||
char[] trial = trialBoard.toCharArray();
|
||||
trial[y * nCols + x] = ' ';
|
||||
trial[newPlayerPos] = '@';
|
||||
|
||||
return new String(trial);
|
||||
}
|
||||
|
||||
String push(int x, int y, int dx, int dy, String trialBoard) {
|
||||
|
||||
int newBoxPos = (y + 2 * dy) * nCols + x + 2 * dx;
|
||||
|
||||
if (trialBoard.charAt(newBoxPos) != ' ')
|
||||
return null;
|
||||
|
||||
char[] trial = trialBoard.toCharArray();
|
||||
trial[y * nCols + x] = ' ';
|
||||
trial[(y + dy) * nCols + x + dx] = '@';
|
||||
trial[newBoxPos] = '$';
|
||||
|
||||
return new String(trial);
|
||||
}
|
||||
|
||||
boolean isSolved(String trialBoard) {
|
||||
for (int i = 0; i < trialBoard.length(); i++)
|
||||
if ((destBoard.charAt(i) == '.')
|
||||
!= (trialBoard.charAt(i) == '$'))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
String solve() {
|
||||
class Board {
|
||||
String cur, sol;
|
||||
int x, y;
|
||||
|
||||
Board(String s1, String s2, int px, int py) {
|
||||
cur = s1;
|
||||
sol = s2;
|
||||
x = px;
|
||||
y = py;
|
||||
}
|
||||
}
|
||||
char[][] dirLabels = {{'u', 'U'}, {'r', 'R'}, {'d', 'D'}, {'l', 'L'}};
|
||||
int[][] dirs = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}};
|
||||
|
||||
Set<String> history = new HashSet<>();
|
||||
LinkedList<Board> open = new LinkedList<>();
|
||||
|
||||
history.add(currBoard);
|
||||
open.add(new Board(currBoard, "", playerX, playerY));
|
||||
|
||||
while (!open.isEmpty()) {
|
||||
Board item = open.poll();
|
||||
String cur = item.cur;
|
||||
String sol = item.sol;
|
||||
int x = item.x;
|
||||
int y = item.y;
|
||||
|
||||
for (int i = 0; i < dirs.length; i++) {
|
||||
String trial = cur;
|
||||
int dx = dirs[i][0];
|
||||
int dy = dirs[i][1];
|
||||
|
||||
// are we standing next to a box ?
|
||||
if (trial.charAt((y + dy) * nCols + x + dx) == '$') {
|
||||
|
||||
// can we push it ?
|
||||
if ((trial = push(x, y, dx, dy, trial)) != null) {
|
||||
|
||||
// or did we already try this one ?
|
||||
if (!history.contains(trial)) {
|
||||
|
||||
String newSol = sol + dirLabels[i][1];
|
||||
|
||||
if (isSolved(trial))
|
||||
return newSol;
|
||||
|
||||
open.add(new Board(trial, newSol, x + dx, y + dy));
|
||||
history.add(trial);
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise try changing position
|
||||
} else if ((trial = move(x, y, dx, dy, trial)) != null) {
|
||||
|
||||
if (!history.contains(trial)) {
|
||||
String newSol = sol + dirLabels[i][0];
|
||||
open.add(new Board(trial, newSol, x + dx, y + dy));
|
||||
history.add(trial);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "No solution";
|
||||
}
|
||||
|
||||
public static void main(String[] a) {
|
||||
String level = "#######,# #,# #,#. # #,#. $$ #,"
|
||||
+ "#.$$ #,#.# @#,#######";
|
||||
System.out.println(new Sokoban(level.split(",")).solve());
|
||||
}
|
||||
}
|
||||
93
Task/Sokoban/Julia/sokoban.julia
Normal file
93
Task/Sokoban/Julia/sokoban.julia
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
struct BoardState
|
||||
board::String
|
||||
csol::String
|
||||
position::Int
|
||||
end
|
||||
|
||||
function move(s::BoardState, dpos)
|
||||
buffer = Vector{UInt8}(deepcopy(s.board))
|
||||
if s.board[s.position] == '@'
|
||||
buffer[s.position] = ' '
|
||||
else
|
||||
buffer[s.position] = '.'
|
||||
end
|
||||
newpos = s.position + dpos
|
||||
if s.board[newpos] == ' '
|
||||
buffer[newpos] = '@'
|
||||
else
|
||||
buffer[newpos] = '+'
|
||||
end
|
||||
String(buffer)
|
||||
end
|
||||
|
||||
function push(s::BoardState, dpos)
|
||||
newpos = s.position + dpos
|
||||
boxpos = newpos + dpos
|
||||
if s.board[boxpos] != ' ' && s.board[boxpos] != '.'
|
||||
return ""
|
||||
end
|
||||
buffer = Vector{UInt8}(deepcopy(s.board))
|
||||
if s.board[s.position] == '@'
|
||||
buffer[s.position] = ' '
|
||||
else
|
||||
buffer[s.position] = '.'
|
||||
end
|
||||
if s.board[newpos] == '$'
|
||||
buffer[newpos] = '@'
|
||||
else
|
||||
buffer[newpos] = '+'
|
||||
end
|
||||
if s.board[boxpos] == ' '
|
||||
buffer[boxpos] = '$'
|
||||
else
|
||||
buffer[boxpos] = '*'
|
||||
end
|
||||
String(buffer)
|
||||
end
|
||||
|
||||
function solve(board)
|
||||
width = findfirst("\n", board[2:end])[1] + 1
|
||||
dopt = (u = -width, l = -1, d = width, r = 1)
|
||||
visited = Dict(board => true)
|
||||
open::Vector{BoardState} = [BoardState(board, "", findfirst("@", board)[1])]
|
||||
while length(open) > 0
|
||||
s1 = open[1]
|
||||
open = open[2:end]
|
||||
for dir in keys(dopt)
|
||||
newpos = s1.position + dopt[dir]
|
||||
x = s1.board[newpos]
|
||||
if x == '$' || x == '*'
|
||||
newboard = push(s1, dopt[dir])
|
||||
if newboard == "" || haskey(visited, newboard)
|
||||
continue
|
||||
end
|
||||
newsol = s1.csol * uppercase(string(dir))
|
||||
if findfirst(r"[\.\+]", newboard) == nothing
|
||||
return newsol
|
||||
end
|
||||
elseif x == ' ' || x == '.'
|
||||
newboard = move(s1, dopt[dir])
|
||||
if haskey(visited, newboard)
|
||||
continue
|
||||
end
|
||||
newsol = s1.csol * string(dir)
|
||||
else
|
||||
continue
|
||||
end
|
||||
open = push!(open, BoardState(newboard, newsol, newpos))
|
||||
visited[newboard] = true
|
||||
end
|
||||
end
|
||||
"No solution" # we should only get here if no solution to the sokoban
|
||||
end
|
||||
|
||||
const testlevel = strip(raw"""
|
||||
#######
|
||||
# #
|
||||
# #
|
||||
#. # #
|
||||
#. $$ #
|
||||
#.$$ #
|
||||
#.# @#
|
||||
#######""")
|
||||
println("For sokoban level:\n$testlevel\n...solution is :\n$(solve(testlevel))")
|
||||
116
Task/Sokoban/Kotlin/sokoban.kotlin
Normal file
116
Task/Sokoban/Kotlin/sokoban.kotlin
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// version 1.2.0
|
||||
|
||||
import java.util.LinkedList
|
||||
|
||||
class Sokoban(board: List<String>) {
|
||||
val destBoard: String
|
||||
val currBoard: String
|
||||
val nCols = board[0].length
|
||||
var playerX = 0
|
||||
var playerY = 0
|
||||
|
||||
init {
|
||||
val destBuf = StringBuilder()
|
||||
val currBuf = StringBuilder()
|
||||
for (r in 0 until board.size) {
|
||||
for (c in 0 until nCols) {
|
||||
val ch = board[r][c]
|
||||
destBuf.append(if (ch != '$' && ch != '@') ch else ' ')
|
||||
currBuf.append(if (ch != '.') ch else ' ')
|
||||
if (ch == '@') {
|
||||
playerX = c
|
||||
playerY = r
|
||||
}
|
||||
}
|
||||
}
|
||||
destBoard = destBuf.toString()
|
||||
currBoard = currBuf.toString()
|
||||
}
|
||||
|
||||
fun move(x: Int, y: Int, dx: Int, dy: Int, trialBoard: String): String {
|
||||
val newPlayerPos = (y + dy) * nCols + x + dx
|
||||
if (trialBoard[newPlayerPos] != ' ') return ""
|
||||
val trial = trialBoard.toCharArray()
|
||||
trial[y * nCols + x] = ' '
|
||||
trial[newPlayerPos] = '@'
|
||||
return String(trial)
|
||||
}
|
||||
|
||||
fun push(x: Int, y: Int, dx: Int, dy: Int, trialBoard: String): String {
|
||||
val newBoxPos = (y + 2 * dy) * nCols + x + 2 * dx
|
||||
if (trialBoard[newBoxPos] != ' ') return ""
|
||||
val trial = trialBoard.toCharArray()
|
||||
trial[y * nCols + x] = ' '
|
||||
trial[(y + dy) * nCols + x + dx] = '@'
|
||||
trial[newBoxPos] = '$'
|
||||
return String(trial)
|
||||
}
|
||||
|
||||
fun isSolved(trialBoard: String): Boolean {
|
||||
for (i in 0 until trialBoard.length) {
|
||||
if ((destBoard[i] == '.') != (trialBoard[i] == '$')) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun solve(): String {
|
||||
data class Board(val cur: String, val sol: String, val x: Int, val y: Int)
|
||||
val dirLabels = listOf('u' to 'U', 'r' to 'R', 'd' to 'D', 'l' to 'L')
|
||||
val dirs = listOf(0 to -1, 1 to 0, 0 to 1, -1 to 0)
|
||||
val history = mutableSetOf<String>()
|
||||
history.add(currBoard)
|
||||
val open = LinkedList<Board>()
|
||||
open.add(Board(currBoard, "", playerX, playerY))
|
||||
|
||||
while (!open.isEmpty()) {
|
||||
val (cur, sol, x, y) = open.poll()
|
||||
for (i in 0 until dirs.size) {
|
||||
var trial = cur
|
||||
val dx = dirs[i].first
|
||||
val dy = dirs[i].second
|
||||
|
||||
// are we standing next to a box ?
|
||||
if (trial[(y + dy) * nCols + x + dx] == '$') {
|
||||
|
||||
// can we push it ?
|
||||
trial = push(x, y, dx, dy, trial)
|
||||
if (!trial.isEmpty()) {
|
||||
|
||||
// or did we already try this one ?
|
||||
if (trial !in history) {
|
||||
val newSol = sol + dirLabels[i].second
|
||||
if (isSolved(trial)) return newSol
|
||||
open.add(Board(trial, newSol, x + dx, y + dy))
|
||||
history.add(trial)
|
||||
}
|
||||
}
|
||||
} // otherwise try changing position
|
||||
else {
|
||||
trial = move(x, y, dx, dy, trial)
|
||||
if (!trial.isEmpty() && trial !in history) {
|
||||
val newSol = sol + dirLabels[i].first
|
||||
open.add(Board(trial, newSol, x + dx, y + dy))
|
||||
history.add(trial)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "No solution"
|
||||
}
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val level = listOf(
|
||||
"#######",
|
||||
"# #",
|
||||
"# #",
|
||||
"#. # #",
|
||||
"#. $$ #",
|
||||
"#.$$ #",
|
||||
"#.# @#",
|
||||
"#######"
|
||||
)
|
||||
println(level.joinToString("\n"))
|
||||
println()
|
||||
println(Sokoban(level).solve())
|
||||
}
|
||||
99
Task/Sokoban/Nim/sokoban.nim
Normal file
99
Task/Sokoban/Nim/sokoban.nim
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import deques, sets, strutils
|
||||
|
||||
type
|
||||
|
||||
Sokoban = object
|
||||
destBoard: string
|
||||
currBoard: string
|
||||
nCols: Natural
|
||||
playerX: Natural
|
||||
playerY: Natural
|
||||
|
||||
Board = tuple[cur, sol: string; x, y: int]
|
||||
|
||||
|
||||
func initSokoban(board: openArray[string]): Sokoban =
|
||||
result.nCols = board[0].len
|
||||
for row in 0..board.high:
|
||||
for col in 0..<result.nCols:
|
||||
let ch = board[row][col]
|
||||
result.destBoard.add if ch notin ['$', '@']: ch else: ' '
|
||||
result.currBoard.add if ch != '.': ch else: ' '
|
||||
if ch == '@':
|
||||
result.playerX = col
|
||||
result.playerY = row
|
||||
|
||||
|
||||
func move(sokoban: Sokoban; x, y, dx, dy: int; trialBoard: string): string =
|
||||
let newPlayerPos = (y + dy) * sokoban.nCols + x + dx
|
||||
if trialBoard[newPlayerPos] != ' ': return
|
||||
result = trialBoard
|
||||
result[y * sokoban.nCols + x] = ' '
|
||||
result[newPlayerPos] = '@'
|
||||
|
||||
|
||||
func push(sokoban: Sokoban; x, y, dx, dy: int; trialBoard: string): string =
|
||||
let newBoxPos = (y + 2 * dy) * sokoban.nCols + x + 2 * dx
|
||||
if trialBoard[newBoxPos] != ' ': return
|
||||
result = trialBoard
|
||||
result[y * sokoban.nCols + x] = ' '
|
||||
result[(y + dy) * sokoban.nCols + x + dx] = '@'
|
||||
result[newBoxPos] = '$'
|
||||
|
||||
|
||||
func isSolved(sokoban: Sokoban; trialBoard: string): bool =
|
||||
for i in 0..trialBoard.high:
|
||||
if (sokoban.destBoard[i] == '.') != (trialBoard[i] == '$'): return false
|
||||
result = true
|
||||
|
||||
|
||||
func solve(sokoban: Sokoban): string =
|
||||
var history: HashSet[string]
|
||||
history.incl sokoban.currBoard
|
||||
const Dirs = [(0, -1, 'u', 'U'), (1, 0, 'r', 'R'), (0, 1, 'd', 'D'), (-1, 0, 'l', 'L')]
|
||||
var open: Deque[Board]
|
||||
open.addLast (sokoban.currBoard, "", sokoban.playerX, sokoban.playerY)
|
||||
|
||||
while open.len != 0:
|
||||
let (cur, sol, x, y) = open.popFirst()
|
||||
for dir in Dirs:
|
||||
var trial = cur
|
||||
let dx = dir[0]
|
||||
let dy = dir[1]
|
||||
|
||||
# Are we standing next to a box?
|
||||
if trial[(y + dy) * sokoban.nCols + x + dx] == '$':
|
||||
# Can we push it?
|
||||
trial = sokoban.push(x, y, dx, dy, trial)
|
||||
if trial.len != 0:
|
||||
# Or did we already try this one?
|
||||
if trial notin history:
|
||||
let newSol = sol & dir[3]
|
||||
if sokoban.isSolved(trial): return newSol
|
||||
open.addLast (trial, newSol, x + dx, y + dy)
|
||||
history.incl trial
|
||||
|
||||
else:
|
||||
# Try to change position.
|
||||
trial = sokoban.move(x, y, dx, dy, trial)
|
||||
if trial.len != 0 and trial notin history:
|
||||
let newSol = sol & dir[2]
|
||||
open.addLast (trial, newSol, x + dx, y + dy)
|
||||
history.incl trial
|
||||
|
||||
result = "no solution"
|
||||
|
||||
when isMainModule:
|
||||
|
||||
const Level = ["#######",
|
||||
"# #",
|
||||
"# #",
|
||||
"#. # #",
|
||||
"#. $$ #",
|
||||
"#.$$ #",
|
||||
"#.# @#",
|
||||
"#######"]
|
||||
|
||||
echo Level.join("\n")
|
||||
echo()
|
||||
echo initSokoban(Level).solve()
|
||||
94
Task/Sokoban/OCaml/sokoban.ocaml
Normal file
94
Task/Sokoban/OCaml/sokoban.ocaml
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
type dir = U | D | L | R
|
||||
type move_t = Move of dir | Push of dir
|
||||
|
||||
let letter = function
|
||||
| Push(U) -> 'U' | Push(D) -> 'D' | Push(L) -> 'L' | Push(R) -> 'R'
|
||||
| Move(U) -> 'u' | Move(D) -> 'd' | Move(L) -> 'l' | Move(R) -> 'r'
|
||||
|
||||
let cols = ref 0
|
||||
let delta = function U -> -(!cols) | D -> !cols | L -> -1 | R -> 1
|
||||
|
||||
let store = Hashtbl.create 251
|
||||
let mark t = Hashtbl.replace store t ()
|
||||
let marked t = Hashtbl.mem store t
|
||||
|
||||
let show ml =
|
||||
List.iter (fun c -> print_char (letter c)) (List.rev ml); print_newline()
|
||||
|
||||
let gen_moves (x,boxes) bd =
|
||||
let empty i = bd.(i) = ' ' && not (List.mem i boxes) in
|
||||
let check l dir =
|
||||
let dx = delta dir in
|
||||
let x1 = x+dx in
|
||||
if List.mem x1 boxes then (
|
||||
if empty (x1+dx) then Push(dir) :: l else l
|
||||
) else (
|
||||
if bd.(x1) = ' ' then Move(dir) :: l else l
|
||||
) in
|
||||
(List.fold_left check [] [U; L; R; D])
|
||||
|
||||
let do_move (x,boxes) = function
|
||||
| Push(d) -> let dx = delta d in
|
||||
let x1 = x+dx in let x2 = x1+dx in
|
||||
let rec shift = function
|
||||
| [] -> failwith "shift"
|
||||
| h :: t -> if h = x1 then x2 :: t else h :: shift t in
|
||||
x1, List.fast_sort compare (shift boxes)
|
||||
| Move(d) -> (x+(delta d)), boxes
|
||||
|
||||
let init_pos bd =
|
||||
let p = ref 0 in
|
||||
let q = ref [] in
|
||||
let check i c =
|
||||
if c = '$' || c = '*' then q := i::!q
|
||||
else if c = '@' then p := i in (
|
||||
Array.iteri check bd;
|
||||
(!p, List.fast_sort compare !q);
|
||||
)
|
||||
|
||||
let final_box bd =
|
||||
let check (i,l) c = if c = '.' || c = '*' then (i+1,i::l) else (i+1,l) in
|
||||
List.fast_sort compare (snd (Array.fold_left check (0,[]) bd))
|
||||
|
||||
let array_of_input inp =
|
||||
let r = List.length inp and c = String.length (List.hd inp) in
|
||||
let a = Array.create (r*c) ' ' in (
|
||||
for i = 0 to pred r do
|
||||
let s = List.nth inp i in
|
||||
for j = 0 to pred c do a.(i*c+j) <- s.[j] done
|
||||
done;
|
||||
cols := c; a)
|
||||
|
||||
let solve b =
|
||||
let board = array_of_input b in
|
||||
let targets = final_box board in
|
||||
let solved pos = targets = snd pos in
|
||||
let clear = Array.map (function '#' -> '#' | _ -> ' ') in
|
||||
let bdc = clear board in
|
||||
let q = Queue.create () in
|
||||
let pos1 = init_pos board in
|
||||
begin
|
||||
mark pos1;
|
||||
Queue.add (pos1, []) q;
|
||||
while not (Queue.is_empty q) do
|
||||
let curr, mhist = Queue.pop q in
|
||||
let moves = gen_moves curr bdc in
|
||||
let check m =
|
||||
let next = do_move curr m in
|
||||
if not (marked next) then
|
||||
if solved next then (show (m::mhist); exit 0)
|
||||
else (mark next; Queue.add (next,m::mhist) q) in
|
||||
List.iter check moves
|
||||
done;
|
||||
print_endline "No solution"
|
||||
end;;
|
||||
|
||||
let level = ["#######";
|
||||
"# #";
|
||||
"# #";
|
||||
"#. # #";
|
||||
"#. $$ #";
|
||||
"#.$$ #";
|
||||
"#.# @#";
|
||||
"#######"] in
|
||||
solve level
|
||||
204
Task/Sokoban/Perl/sokoban.pl
Normal file
204
Task/Sokoban/Perl/sokoban.pl
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
#!perl
|
||||
use strict;
|
||||
use warnings qw(FATAL all);
|
||||
my @initial = split /\n/, <<'';
|
||||
#############
|
||||
# # #
|
||||
# $$$$$$$ @#
|
||||
#....... #
|
||||
#############
|
||||
|
||||
#######
|
||||
# #
|
||||
# #
|
||||
#. # #
|
||||
#. $$ #
|
||||
#.$$ #
|
||||
#.# @#
|
||||
#######
|
||||
|
||||
=for
|
||||
space is an empty square
|
||||
# is a wall
|
||||
@ is the player
|
||||
$ is a box
|
||||
. is a goal
|
||||
+ is the player on a goal
|
||||
* is a box on a goal
|
||||
=cut
|
||||
|
||||
|
||||
my $cols = length($initial[0]);
|
||||
my $initial = join '', @initial;
|
||||
my $size = length($initial);
|
||||
die unless $size == $cols * @initial;
|
||||
|
||||
sub WALL() { 1 }
|
||||
sub PLAYER() { 2 }
|
||||
sub BOX() { 4 }
|
||||
sub GOAL() { 8 }
|
||||
|
||||
my %input = (
|
||||
' ' => 0, '#' => WALL, '@' => PLAYER, '$' => BOX,
|
||||
'.' => GOAL, '+' => PLAYER|GOAL, '*' => BOX|GOAL,
|
||||
);
|
||||
my %output = reverse(%input);
|
||||
|
||||
sub packed_initial {
|
||||
my $ret = '';
|
||||
vec( $ret, $_, 4 ) = $input{substr $initial, $_, 1}
|
||||
for( 0 .. $size-1 );
|
||||
$ret;
|
||||
}
|
||||
|
||||
sub printable_board {
|
||||
my $board = shift;
|
||||
my @c = @output{map vec($board, $_, 4), 0 .. $size-1};
|
||||
my $ret = '';
|
||||
while( my @row = splice @c, 0, $cols ) {
|
||||
$ret .= join '', @row, "\n";
|
||||
}
|
||||
$ret;
|
||||
}
|
||||
|
||||
my $packed = packed_initial();
|
||||
|
||||
my @udlr = qw(u d l r);
|
||||
my @UDLR = qw(U D L R);
|
||||
my @deltas = (-$cols, +$cols, -1, +1);
|
||||
|
||||
my %fseen;
|
||||
INIT_FORWARD: {
|
||||
$initial =~ /(\@|\+)/ or die;
|
||||
use vars qw(@ftodo @fnext);
|
||||
@ftodo = (["", $packed, $-[0]]);
|
||||
$fseen{$packed} = '';
|
||||
}
|
||||
|
||||
my %rseen;
|
||||
INIT_REVERSE: {
|
||||
my $goal = $packed;
|
||||
vec($goal, $ftodo[0][2], 4) -= PLAYER;
|
||||
my @u = grep { my $t = vec($goal, $_, 4); $t & GOAL and not $t & BOX } 0 .. $size-1;
|
||||
my @b = grep { my $t = vec($goal, $_, 4); $t & BOX and not $t & GOAL } 0 .. $size-1;
|
||||
die unless @u == @b;
|
||||
vec($goal, $_, 4) += BOX for @u;
|
||||
vec($goal, $_, 4) -= BOX for @b;
|
||||
use vars qw(@rtodo @rnext);
|
||||
FINAL_PLACE: for my $player (0 .. $size-1) {
|
||||
next if vec($goal, $player, 4);
|
||||
FIND_GOAL: {
|
||||
vec($goal, $player + $_, 4) & GOAL and last FIND_GOAL for @deltas;
|
||||
next FINAL_PLACE;
|
||||
}
|
||||
my $a_goal = $goal;
|
||||
vec($a_goal, $player, 4) += PLAYER;
|
||||
push @rtodo, ["", $a_goal, $player ];
|
||||
$rseen{$a_goal} = '';
|
||||
#print printable_board($a_goal);
|
||||
}
|
||||
}
|
||||
|
||||
my $movelen = -1;
|
||||
my ($solution);
|
||||
MAIN: while( @ftodo and @rtodo ) {
|
||||
|
||||
FORWARD: {
|
||||
my ($moves, $level, $player) = @{pop @ftodo};
|
||||
die unless vec($level, $player, 4) & PLAYER;
|
||||
|
||||
for my $dir_num (0 .. 3) {
|
||||
my $delta = $deltas[$dir_num];
|
||||
my @loc = map $player + $delta * $_, 0 .. 2;
|
||||
my @val = map vec($level, $_, 4), @loc;
|
||||
|
||||
next if $val[1] & WALL or ($val[1] & BOX and $val[2] & (BOX|WALL));
|
||||
|
||||
my $new = $level;
|
||||
vec($new, $loc[0], 4) -= PLAYER;
|
||||
vec($new, $loc[1], 4) += PLAYER;
|
||||
my $nmoves;
|
||||
if( $val[1] & BOX ) {
|
||||
vec($new, $loc[1], 4) -= BOX;
|
||||
vec($new, $loc[2], 4) += BOX;
|
||||
$nmoves = $moves . $UDLR[$dir_num];
|
||||
} else {
|
||||
$nmoves = $moves . $udlr[$dir_num];
|
||||
}
|
||||
|
||||
next if exists $fseen{$new};
|
||||
$fseen{$new} = $nmoves;
|
||||
|
||||
push @fnext, [ $nmoves, $new, $loc[1] ];
|
||||
|
||||
exists $rseen{$new} or next;
|
||||
#print(($val[1] & BOX) ? "Push $UDLR[$dir_num]\n" : "Fwalk $udlr[$dir_num]\n");
|
||||
$solution = $new;
|
||||
last MAIN;
|
||||
}
|
||||
|
||||
last FORWARD if @ftodo;
|
||||
use vars qw(*ftodo *fnext);
|
||||
(*ftodo, *fnext) = (\@fnext, \@ftodo);
|
||||
} # end FORWARD
|
||||
|
||||
BACKWARD: {
|
||||
my ($moves, $level, $player) = @{pop @rtodo};
|
||||
die "<$level>" unless vec($level, $player, 4) & PLAYER;
|
||||
|
||||
for my $dir_num (0 .. 3) {
|
||||
my $delta = $deltas[$dir_num];
|
||||
# look behind and in front of the player.
|
||||
my @loc = map $player + $delta * $_, -1 .. 1;
|
||||
my @val = map vec($level, $_, 4), @loc;
|
||||
|
||||
# unlike the forward solution, we cannot push boxes
|
||||
next if $val[0] & (WALL|BOX);
|
||||
my $new = $level;
|
||||
vec($new, $loc[0], 4) += PLAYER;
|
||||
vec($new, $loc[1], 4) -= PLAYER;
|
||||
# unlike the forward solution, if we have a box behind us
|
||||
# we can *either* pull it or not. This means there are
|
||||
# two "successors" to this board.
|
||||
if( $val[2] & BOX ) {
|
||||
my $pull = $new;
|
||||
vec($pull, $loc[2], 4) -= BOX;
|
||||
vec($pull, $loc[1], 4) += BOX;
|
||||
goto RWALK if exists $rseen{$pull};
|
||||
my $pmoves = $UDLR[$dir_num] . $moves;
|
||||
$rseen{$pull} = $pmoves;
|
||||
push @rnext, [$pmoves, $pull, $loc[0]];
|
||||
goto RWALK unless exists $fseen{$pull};
|
||||
print "Doing pull\n";
|
||||
$solution = $pull;
|
||||
last MAIN;
|
||||
}
|
||||
RWALK:
|
||||
next if exists $rseen{$new}; # next direction.
|
||||
my $wmoves = $udlr[$dir_num] . $moves;
|
||||
$rseen{$new} = $wmoves;
|
||||
push @rnext, [$wmoves, $new, $loc[0]];
|
||||
next unless exists $fseen{$new};
|
||||
print "Rwalk\n";
|
||||
$solution = $new;
|
||||
last MAIN;
|
||||
}
|
||||
|
||||
last BACKWARD if @rtodo;
|
||||
use vars qw(*rtodo *rnext);
|
||||
(*rtodo, *rnext) = (\@rnext, \@rtodo);
|
||||
} # end BACKWARD
|
||||
}
|
||||
|
||||
if( $solution ) {
|
||||
my $fmoves = $fseen{$solution};
|
||||
my $rmoves = $rseen{$solution};
|
||||
print "Solution found!\n";
|
||||
print "Time: ", (time() - $^T), " seconds\n";
|
||||
print "Moves: $fmoves $rmoves\n";
|
||||
print "Move Length: ", length($fmoves . $rmoves), "\n";
|
||||
print "Middle Board: \n", printable_board($solution);
|
||||
} else {
|
||||
print "No solution found!\n";
|
||||
}
|
||||
__END__
|
||||
171
Task/Sokoban/Phix/sokoban-1.phix
Normal file
171
Task/Sokoban/Phix/sokoban-1.phix
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
-- demo\rosetta\Sokoban.exw
|
||||
integer w, h -- (set from parsing the input grid)
|
||||
sequence moves -- "", as +/-w and +/-1 (udlr)
|
||||
string live -- "", Y if box can go there
|
||||
|
||||
function reachable(sequence pushes, string level)
|
||||
integer p = find_any("@+",level)
|
||||
string ok = repeat('N',length(level))
|
||||
ok[p] = 'Y'
|
||||
while true do
|
||||
p = find('Y',ok)
|
||||
if p=0 then exit end if
|
||||
ok[p] = 'y'
|
||||
for i=1 to length(moves) do
|
||||
integer pn = p+moves[i]
|
||||
if ok[pn]='N'
|
||||
and find(level[pn]," .") then
|
||||
ok[pn] = 'Y'
|
||||
end if
|
||||
end for
|
||||
end while
|
||||
for i=length(pushes)-1 to 1 by -2 do
|
||||
if ok[pushes[i]-pushes[i+1]]!='y' then
|
||||
pushes[i..i+1] = {}
|
||||
end if
|
||||
end for
|
||||
return pushes
|
||||
end function
|
||||
|
||||
function pushable(string level)
|
||||
sequence res = {}
|
||||
for i=1 to length(level) do
|
||||
if find(level[i],"$*") then
|
||||
if find(level[i-w]," .@+")
|
||||
and find(level[i+w]," .@+") then
|
||||
if live[i-w]='Y' then res &= {i,-w} end if
|
||||
if live[i+w]='Y' then res &= {i,+w} end if
|
||||
end if
|
||||
if find(level[i-1]," .@+")
|
||||
and find(level[i+1]," .@+") then
|
||||
if live[i-1]='Y' then res &= {i,-1} end if
|
||||
if live[i+1]='Y' then res &= {i,+1} end if
|
||||
end if
|
||||
end if
|
||||
end for
|
||||
return reachable(res,level)
|
||||
end function
|
||||
|
||||
function solve(string level)
|
||||
atom t2 = time()+2
|
||||
integer seen = new_dict()
|
||||
sequence solution = "No solution.", partial = {}
|
||||
sequence todo = {{level,partial,pushable(level)}}, pushes
|
||||
while length(todo) do
|
||||
sequence t1 = todo[1]
|
||||
todo = todo[2..$]
|
||||
{level,partial,pushes} = t1
|
||||
integer p = find_any("@+",level)
|
||||
while length(pushes) do
|
||||
integer {s,m} = pushes[1..2]
|
||||
pushes = pushes[3..$]
|
||||
level[p] = " ."[find(level[p],"@+")]
|
||||
level[s] = "@+"[find(level[s],"$*")]
|
||||
level[s+m] = "$*"[find(level[s+m]," .")]
|
||||
if getd_index(level,seen)=0 then
|
||||
sequence np = partial&{s,m}
|
||||
if not find('$',level) then
|
||||
solution = np
|
||||
todo = {}
|
||||
pushes = {}
|
||||
exit
|
||||
end if
|
||||
setd(level,true,seen)
|
||||
if time()>t2 then
|
||||
printf(1,"working... (seen %d)\r",dict_size(seen))
|
||||
t2 = time()+2
|
||||
end if
|
||||
todo = append(todo,{level,np,pushable(level)})
|
||||
end if
|
||||
level = t1[1] -- (reset)
|
||||
end while
|
||||
end while
|
||||
destroy_dict(seen)
|
||||
return solution
|
||||
end function
|
||||
|
||||
procedure plays(string level, sequence solution)
|
||||
-- This plays push-only solutions (see play() for lurd)
|
||||
string res = level
|
||||
integer p = find_any("@+",level)
|
||||
for i=1 to length(solution) by 2 do
|
||||
integer {s,m} = solution[i..i+1] m+=s
|
||||
level[p] = " ."[find(level[p],"@+")]
|
||||
level[s] = "@+"[find(level[s],"$*")]
|
||||
level[m] = "$*"[find(level[m]," .")]
|
||||
res &= level
|
||||
p = s
|
||||
end for
|
||||
-- (replacing +0 with 1/2/3 may help in some cases)
|
||||
puts(1,join_by(split(res,'\n'),h,floor(80/(w+2))+0))
|
||||
end procedure
|
||||
|
||||
procedure mark_live(integer p, string level)
|
||||
-- (idea cribbed from the C version)
|
||||
if live[p]='N' then
|
||||
live[p] = 'Y'
|
||||
integer l = length(level)
|
||||
if p-w*2>=1 and level[p-w]!='#' and level[p-w*2]!='#' then mark_live(p-w,level) end if
|
||||
if p+w*2<=l and level[p+w]!='#' and level[p+w*2]!='#' then mark_live(p+w,level) end if
|
||||
if p-2 >=1 and level[p-1]!='#' and level[p-2] !='#' then mark_live(p-1,level) end if
|
||||
if p+2 <=l and level[p+1]!='#' and level[p+2] !='#' then mark_live(p+1,level) end if
|
||||
end if
|
||||
end procedure
|
||||
|
||||
function make_square(string level)
|
||||
--
|
||||
-- Sets {h, w, moves, live}, and returns an evened-out/rectangular level
|
||||
--
|
||||
if level[$]!='\n' then level &= '\n' end if -- (for the display)
|
||||
sequence lines = split(level,'\n')
|
||||
h = length(lines)-1 -- set height (ignore trailing \n)
|
||||
sequence ln = repeat(0,h)
|
||||
for i=1 to h do
|
||||
ln[i] = {length(lines[i]),i}
|
||||
for j=1 to length(lines[i]) do
|
||||
-- validate each line, why not
|
||||
if not find(lines[i,j]," #.$@*") then
|
||||
crash("invalid input")
|
||||
end if
|
||||
end for
|
||||
end for
|
||||
ln = sort(ln)
|
||||
w = ln[$][1]+1 -- set width (==longest, inc \n)
|
||||
moves = {-w,+w,-1,+1} -- and make these (udlr) legal ...
|
||||
for i=1 to h do
|
||||
integer {l,n} = ln[i], pad = w-1-l
|
||||
if pad=0 then exit end if
|
||||
lines[n] &= repeat(' ',pad) -- ... by evening up the "grid"
|
||||
end for
|
||||
level = join(lines,'\n')
|
||||
live = join(repeat(repeat('N',w-1),h),'\n')
|
||||
for p=1 to length(level) do
|
||||
if find(level[p],".+*") then
|
||||
mark_live(p,level)
|
||||
end if
|
||||
end for
|
||||
return level
|
||||
end function
|
||||
|
||||
constant input = """
|
||||
#######
|
||||
# #
|
||||
# #
|
||||
#. # #
|
||||
#. $$ #
|
||||
#.$$ #
|
||||
#.# @#
|
||||
#######
|
||||
"""
|
||||
|
||||
atom t0 = time()
|
||||
string level = make_square(input)
|
||||
sequence pushset = solve(level)
|
||||
integer pop = length(pushset)/2
|
||||
if string(pushset) then
|
||||
puts(1,level)
|
||||
printf(1,"%s\n",{pushset}) -- ("No Solution.")
|
||||
else
|
||||
printf(1,"solution of %d pushes (%s)\n",{pop,elapsed(time()-t0)})
|
||||
plays(level,pushset)
|
||||
end if
|
||||
7
Task/Sokoban/Phix/sokoban-2.phix
Normal file
7
Task/Sokoban/Phix/sokoban-2.phix
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
constant input = """
|
||||
#############
|
||||
# # #
|
||||
# $$$$$$$ @#
|
||||
#....... #
|
||||
#############
|
||||
"""
|
||||
10
Task/Sokoban/Phix/sokoban-3.phix
Normal file
10
Task/Sokoban/Phix/sokoban-3.phix
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
constant input = """
|
||||
####
|
||||
##. ##
|
||||
##### . #
|
||||
# # # #
|
||||
# $ # # #
|
||||
# $ @ #
|
||||
###### ##
|
||||
####
|
||||
"""
|
||||
7
Task/Sokoban/Phix/sokoban-4.phix
Normal file
7
Task/Sokoban/Phix/sokoban-4.phix
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
constant input = """
|
||||
#############
|
||||
#... # #
|
||||
#.$$$$$$$ @#
|
||||
#... #
|
||||
#############
|
||||
"""
|
||||
13
Task/Sokoban/Phix/sokoban-5.phix
Normal file
13
Task/Sokoban/Phix/sokoban-5.phix
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
constant input = """
|
||||
#####
|
||||
# #
|
||||
# #
|
||||
### #$##
|
||||
# #
|
||||
### #$## # ######
|
||||
# # ## ##### .#
|
||||
# $ $ ..#
|
||||
##### ### #@## .#
|
||||
# #########
|
||||
#######
|
||||
"""
|
||||
82
Task/Sokoban/PicoLisp/sokoban-1.l
Normal file
82
Task/Sokoban/PicoLisp/sokoban-1.l
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
(load "@lib/simul.l")
|
||||
|
||||
# Display board
|
||||
(de display ()
|
||||
(disp *Board NIL
|
||||
'((This)
|
||||
(pack
|
||||
(if2 (== This *Pos) (memq This *Goals)
|
||||
"+" # Player on goal
|
||||
"@" # Player elsewhere
|
||||
(if (: val) "*" ".") # On gloal
|
||||
(or (: val) " ") ) # Elsewhere
|
||||
" " ) ) ) )
|
||||
|
||||
# Initialize
|
||||
(de main (Lst)
|
||||
(mapc
|
||||
'((B L)
|
||||
(mapc
|
||||
'((This C)
|
||||
(case C
|
||||
(" ")
|
||||
("." (push '*Goals This))
|
||||
("@" (setq *Pos This))
|
||||
("$" (=: val C) (push '*Boxes This))
|
||||
(T (=: val C)) ) )
|
||||
B L ) )
|
||||
(setq *Board (grid (length (car Lst)) (length Lst)))
|
||||
(apply mapcar (flip (mapcar chop Lst)) list) )
|
||||
(display) )
|
||||
|
||||
# Generate possible push-moves
|
||||
(de pushes ()
|
||||
(make
|
||||
(for Box *Boxes
|
||||
(unless (or (; (west Box) val) (; (east Box) val))
|
||||
(when (moves (east Box))
|
||||
(link (cons (cons Box (west Box)) *Pos "L" @)) )
|
||||
(when (moves (west Box))
|
||||
(link (cons (cons Box (east Box)) *Pos "R" @)) ) )
|
||||
(unless (or (; (south Box) val) (; (north Box) val))
|
||||
(when (moves (north Box))
|
||||
(link (cons (cons Box (south Box)) *Pos "D" @)) )
|
||||
(when (moves (south Box))
|
||||
(link (cons (cons Box (north Box)) *Pos "U" @)) ) ) ) ) )
|
||||
|
||||
# Moves of player to destination
|
||||
(de moves (Dst Hist)
|
||||
(or
|
||||
(== Dst *Pos)
|
||||
(mini length
|
||||
(extract
|
||||
'((Dir)
|
||||
(with ((car Dir) Dst)
|
||||
(cond
|
||||
((== This *Pos) (cons (cdr Dir)))
|
||||
((: val))
|
||||
((memq This Hist))
|
||||
((moves This (cons Dst Hist))
|
||||
(cons (cdr Dir) @) ) ) ) )
|
||||
'((west . "r") (east . "l") (south . "u") (north . "d")) ) ) ) )
|
||||
|
||||
# Find solution
|
||||
(de go (Res)
|
||||
(unless (idx '*Hist (sort (copy *Boxes)) T) # No repeated state
|
||||
(if (find '((This) (<> "$" (: val))) *Goals)
|
||||
(pick
|
||||
'((Psh)
|
||||
(setq # Move
|
||||
*Pos (caar Psh)
|
||||
*Boxes (cons (cdar Psh) (delq *Pos *Boxes)) )
|
||||
(put *Pos 'val NIL)
|
||||
(put (cdar Psh) 'val "$")
|
||||
(prog1 (go (append (cddr Psh) Res))
|
||||
(setq # Undo move
|
||||
*Pos (cadr Psh)
|
||||
*Boxes (cons (caar Psh) (delq (cdar Psh) *Boxes)) )
|
||||
(put (cdar Psh) 'val NIL)
|
||||
(put (caar Psh) 'val "$") ) )
|
||||
(pushes) )
|
||||
(display) # Display solution
|
||||
(pack (flip Res)) ) ) )
|
||||
12
Task/Sokoban/PicoLisp/sokoban-2.l
Normal file
12
Task/Sokoban/PicoLisp/sokoban-2.l
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
(main
|
||||
(quote
|
||||
"#######"
|
||||
"# #"
|
||||
"# #"
|
||||
"#. # #"
|
||||
"#. $$ #"
|
||||
"#.$$ #"
|
||||
"#.# @#"
|
||||
"#######" ) )
|
||||
(prinl)
|
||||
(go)
|
||||
96
Task/Sokoban/Python/sokoban.py
Normal file
96
Task/Sokoban/Python/sokoban.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
from array import array
|
||||
from collections import deque
|
||||
import psyco
|
||||
|
||||
data = []
|
||||
nrows = 0
|
||||
px = py = 0
|
||||
sdata = ""
|
||||
ddata = ""
|
||||
|
||||
def init(board):
|
||||
global data, nrows, sdata, ddata, px, py
|
||||
data = filter(None, board.splitlines())
|
||||
nrows = max(len(r) for r in data)
|
||||
|
||||
maps = {' ':' ', '.': '.', '@':' ', '#':'#', '$':' '}
|
||||
mapd = {' ':' ', '.': ' ', '@':'@', '#':' ', '$':'*'}
|
||||
|
||||
for r, row in enumerate(data):
|
||||
for c, ch in enumerate(row):
|
||||
sdata += maps[ch]
|
||||
ddata += mapd[ch]
|
||||
if ch == '@':
|
||||
px = c
|
||||
py = r
|
||||
|
||||
def push(x, y, dx, dy, data):
|
||||
if sdata[(y+2*dy) * nrows + x+2*dx] == '#' or \
|
||||
data[(y+2*dy) * nrows + x+2*dx] != ' ':
|
||||
return None
|
||||
|
||||
data2 = array("c", data)
|
||||
data2[y * nrows + x] = ' '
|
||||
data2[(y+dy) * nrows + x+dx] = '@'
|
||||
data2[(y+2*dy) * nrows + x+2*dx] = '*'
|
||||
return data2.tostring()
|
||||
|
||||
def is_solved(data):
|
||||
for i in xrange(len(data)):
|
||||
if (sdata[i] == '.') != (data[i] == '*'):
|
||||
return False
|
||||
return True
|
||||
|
||||
def solve():
|
||||
open = deque([(ddata, "", px, py)])
|
||||
visited = set([ddata])
|
||||
dirs = ((0, -1, 'u', 'U'), ( 1, 0, 'r', 'R'),
|
||||
(0, 1, 'd', 'D'), (-1, 0, 'l', 'L'))
|
||||
|
||||
lnrows = nrows
|
||||
while open:
|
||||
cur, csol, x, y = open.popleft()
|
||||
|
||||
for di in dirs:
|
||||
temp = cur
|
||||
dx, dy = di[0], di[1]
|
||||
|
||||
if temp[(y+dy) * lnrows + x+dx] == '*':
|
||||
temp = push(x, y, dx, dy, temp)
|
||||
if temp and temp not in visited:
|
||||
if is_solved(temp):
|
||||
return csol + di[3]
|
||||
open.append((temp, csol + di[3], x+dx, y+dy))
|
||||
visited.add(temp)
|
||||
else:
|
||||
if sdata[(y+dy) * lnrows + x+dx] == '#' or \
|
||||
temp[(y+dy) * lnrows + x+dx] != ' ':
|
||||
continue
|
||||
|
||||
data2 = array("c", temp)
|
||||
data2[y * lnrows + x] = ' '
|
||||
data2[(y+dy) * lnrows + x+dx] = '@'
|
||||
temp = data2.tostring()
|
||||
|
||||
if temp not in visited:
|
||||
if is_solved(temp):
|
||||
return csol + di[2]
|
||||
open.append((temp, csol + di[2], x+dx, y+dy))
|
||||
visited.add(temp)
|
||||
|
||||
return "No solution"
|
||||
|
||||
|
||||
level = """\
|
||||
#######
|
||||
# #
|
||||
# #
|
||||
#. # #
|
||||
#. $$ #
|
||||
#.$$ #
|
||||
#.# @#
|
||||
#######"""
|
||||
|
||||
psyco.full()
|
||||
init(level)
|
||||
print level, "\n\n", solve()
|
||||
107
Task/Sokoban/Racket/sokoban.rkt
Normal file
107
Task/Sokoban/Racket/sokoban.rkt
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#lang racket
|
||||
(require data/heap
|
||||
"../lib/vector2.rkt" "../lib/queue.rkt" (only-in "../lib/util.rkt" push! tstruct ret awhen))
|
||||
|
||||
(define level (list "#######"
|
||||
"# #"
|
||||
"# #"
|
||||
"#. # #"
|
||||
"#. $$ #"
|
||||
"#.$$ #"
|
||||
"#.# @#"
|
||||
"#######"))
|
||||
(define (strings->vec2 l) (lists->vec2 (map string->list l)))
|
||||
;turn everything except walls into distance from goals
|
||||
(define (clear-level l)
|
||||
(ret ([l (vec2-copy l)])
|
||||
(define dots (vec2-atsq l #\.))
|
||||
(define q (list->q (map (λ (p) (cons p 0)) dots)))
|
||||
(let bfs () ;this search has implicit history in the mutated vector2
|
||||
(unless (nilq? q)
|
||||
(match-define (cons p n) (deq! q))
|
||||
(define x (vec2@ l p))
|
||||
;stop if position is either a wall or a previously filled number
|
||||
(cond [(or (eq? x #\#) (number? x)) (bfs)]
|
||||
[else (vec2! l p n)
|
||||
(for-adj l x [p p] #f (enq! (cons p (add1 n)) q))
|
||||
(bfs)])))))
|
||||
|
||||
;corresponds to PicoLisp's move table in "moves", while also adding a push direction mapping
|
||||
(tstruct move (f d))
|
||||
(define-values (mu md ml mr LURD)
|
||||
(let ()
|
||||
(define t (map (λ (x) (cons (car x) (apply pos (cdr x))))
|
||||
'([#\u -1 0] [#\d 1 0] [#\l 0 -1] [#\r 0 1])))
|
||||
(define (mv d)
|
||||
(define x (assoc d t))
|
||||
(move (λ (p) (pos+ p (cdr x))) (car x)))
|
||||
(values (mv #\u) (mv #\d) (mv #\l) (mv #\r)
|
||||
(λ (d) (char-upcase (car (findf (λ (x) (equal? d (cdr x))) t)))))))
|
||||
|
||||
;state = player pos * box poses
|
||||
(tstruct st (p b))
|
||||
(define (st= s1 s2) (andmap (λ (b) (member b (st-b s2))) (st-b s1)))
|
||||
(define (box? p s) (member p (st-b s)))
|
||||
;calculates value of a state for insertion into priority queue
|
||||
;value is sum of box distances from goals
|
||||
(define (value s l) (apply + (map (λ (p) (vec2@ l p)) (st-b s))))
|
||||
;init state for a level
|
||||
(define (st0 l) (st (vec2-atq l #\@) (vec2-atsq l #\$)))
|
||||
(define (make-solution-checker l)
|
||||
(define dots (vec2-atsq l #\.))
|
||||
(λ (s) (andmap (λ (b) (member b dots)) (st-b s))))
|
||||
|
||||
;state after push * lurd history
|
||||
(tstruct push (st h))
|
||||
(define (pushes s l)
|
||||
(ret ([pushes '()])
|
||||
(for ([b (in-list (st-b s))])
|
||||
(for-adj l a [p b] #f
|
||||
(define d (pos- p b)) ;direction of push
|
||||
(define op (pos- b d)) ;where player stands to push
|
||||
(define o (vec2@ l op))
|
||||
;make sure push pos and push dest are clear
|
||||
(when (and (number? a) (number? o)
|
||||
(not (box? p s)) (not (box? op s)))
|
||||
(awhen [@ (moves s op l)]
|
||||
(define new-st (st b (cons p (remove b (st-b s)))))
|
||||
(push! (push new-st (cons (LURD d) @)) pushes)))))))
|
||||
|
||||
;state * goal pos * level -> lurd string
|
||||
(define (moves s g l)
|
||||
(define h '())
|
||||
(define q (list->q (list (list (st-p s)))))
|
||||
(let bfs ()
|
||||
(if (nilq? q)
|
||||
#f
|
||||
(match-let ([(cons p lurd) (deq! q)])
|
||||
(cond [(equal? p g) lurd]
|
||||
[(or (char=? (vec2@ l p) #\#) (box? p s) (member p h)) (bfs)]
|
||||
[else (push! p h)
|
||||
(for-each (λ (m)
|
||||
(match-define (move f s) m)
|
||||
(enq! (cons (f p) (cons s lurd)) q))
|
||||
(list mu md ml mr))
|
||||
(bfs)])))))
|
||||
|
||||
(define (sokoban l)
|
||||
(define-values (clear s0 solved?)
|
||||
(let ([l (strings->vec2 l)])
|
||||
(values (clear-level l) (st0 l) (make-solution-checker l))))
|
||||
(define h '())
|
||||
(tstruct q-elem (s lurd v)) ;priority queue stores state, lurd hist, and value
|
||||
(define (elem<= s1 s2) (<= (q-elem-v s1) (q-elem-v s2))) ;compare wrapped values
|
||||
;queue stores a single element at the beginning consisting of:
|
||||
;1. starting state, 2. empty lurd history, 3. value of starting state
|
||||
(define q (vector->heap elem<= (vector (q-elem s0 '() (value s0 clear)))))
|
||||
(let bfs ()
|
||||
(match-define (q-elem s lurd _) (heap-min q))
|
||||
(heap-remove-min! q)
|
||||
(cond [(solved? s) (list->string (reverse lurd))]
|
||||
[(memf (λ (s1) (st= s s1)) h) (bfs)]
|
||||
[else (push! s h)
|
||||
(for-each (λ (p)
|
||||
(define s (push-st p))
|
||||
(heap-add! q (q-elem s (append (push-h p) lurd) (value s clear))))
|
||||
(pushes s clear))
|
||||
(bfs)])))
|
||||
101
Task/Sokoban/Raku/sokoban.raku
Normal file
101
Task/Sokoban/Raku/sokoban.raku
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
sub MAIN() {
|
||||
my $level = q:to//;
|
||||
#######
|
||||
# #
|
||||
# #
|
||||
#. # #
|
||||
#. $$ #
|
||||
#.$$ #
|
||||
#.# @#
|
||||
#######
|
||||
|
||||
say 'level:';
|
||||
print $level;
|
||||
say 'solution:';
|
||||
say solve($level);
|
||||
}
|
||||
|
||||
class State {
|
||||
has Str $.board;
|
||||
has Str $.sol;
|
||||
has Int $.pos;
|
||||
|
||||
method move(Int $delta --> Str) {
|
||||
my $new = $!board;
|
||||
if $new.substr($!pos,1) eq '@' {
|
||||
substr-rw($new,$!pos,1) = ' ';
|
||||
} else {
|
||||
substr-rw($new,$!pos,1) = '.';
|
||||
}
|
||||
my $pos := $!pos + $delta;
|
||||
if $new.substr($pos,1) eq ' ' {
|
||||
substr-rw($new,$pos,1) = '@';
|
||||
} else {
|
||||
substr-rw($new,$pos,1) = '+';
|
||||
}
|
||||
return $new;
|
||||
}
|
||||
|
||||
method push(Int $delta --> Str) {
|
||||
my $pos := $!pos + $delta;
|
||||
my $box := $pos + $delta;
|
||||
return '' unless $!board.substr($box,1) eq ' ' | '.';
|
||||
my $new = $!board;
|
||||
if $new.substr($!pos,1) eq '@' {
|
||||
substr-rw($new,$!pos,1) = ' ';
|
||||
} else {
|
||||
substr-rw($new,$!pos,1) = '.';
|
||||
}
|
||||
if $new.substr($pos,1) eq '$' {
|
||||
substr-rw($new,$pos,1) = '@';
|
||||
} else {
|
||||
substr-rw($new,$pos,1) = '+';
|
||||
}
|
||||
if $new.substr($box,1) eq ' ' {
|
||||
substr-rw($new,$box,1) = '$';
|
||||
} else {
|
||||
substr-rw($new,$box,1) = '*';
|
||||
}
|
||||
return $new;
|
||||
}
|
||||
}
|
||||
|
||||
sub solve(Str $start --> Str) {
|
||||
my $board = $start;
|
||||
my $width = $board.lines[0].chars + 1;
|
||||
my @dirs =
|
||||
["u", "U", -$width],
|
||||
["r", "R", 1],
|
||||
["d", "D", $width],
|
||||
["l", "L", -1];
|
||||
|
||||
my %visited = $board => True;
|
||||
|
||||
my $pos = $board.index('@');
|
||||
my @open = State.new(:$board, :sol(''), :$pos);
|
||||
while @open {
|
||||
my $state = @open.shift;
|
||||
for @dirs -> [$move, $push, $delta] {
|
||||
my $board;
|
||||
my $sol;
|
||||
my $pos = $state.pos + $delta;
|
||||
given $state.board.substr($pos,1) {
|
||||
when '$' | '*' {
|
||||
$board = $state.push($delta);
|
||||
next if $board eq "" || %visited{$board};
|
||||
$sol = $state.sol ~ $push;
|
||||
return $sol unless $board ~~ /<[ . + ]>/;
|
||||
}
|
||||
when ' ' | '.' {
|
||||
$board = $state.move($delta);
|
||||
next if %visited{$board};
|
||||
$sol = $state.sol ~ $move;
|
||||
}
|
||||
default { next }
|
||||
}
|
||||
@open.push: State.new: :$board, :$sol, :$pos;
|
||||
%visited{$board} = True;
|
||||
}
|
||||
}
|
||||
return "No solution";
|
||||
}
|
||||
382
Task/Sokoban/Ring/sokoban.ring
Normal file
382
Task/Sokoban/Ring/sokoban.ring
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
#--------------------------------------------------#
|
||||
# Sokoban Game #
|
||||
#--------------------------------------------------#
|
||||
|
||||
# Game Data
|
||||
|
||||
aPlayer = [ :Row = 3, :Col = 4 ]
|
||||
|
||||
aLevel1 = [
|
||||
[1,1,1,2,2,2,2,2,1,1,1,1,1,1],
|
||||
[1,2,2,2,1,1,1,2,1,1,1,1,1,1],
|
||||
[1,2,4,3,5,1,1,2,1,1,1,1,1,1],
|
||||
[1,2,2,2,1,5,4,2,1,1,1,1,1,1],
|
||||
[1,2,4,2,2,5,1,2,1,1,1,1,1,1],
|
||||
[1,2,1,2,1,4,1,2,2,1,1,1,1,1],
|
||||
[1,2,5,1,6,5,5,4,2,1,1,1,1,1],
|
||||
[1,2,1,1,1,4,1,1,2,1,1,1,1,1],
|
||||
[1,2,2,2,2,2,2,2,2,1,1,1,1,1],
|
||||
[1,1,1,1,1,1,1,1,1,1,1,1,1,1]
|
||||
]
|
||||
|
||||
aLevel2 = [
|
||||
[1,1,1,2,2,2,2,2,2,2,2,2,1,1],
|
||||
[1,2,2,2,1,5,1,4,1,1,1,2,1,1],
|
||||
[1,2,4,3,5,1,1,1,5,1,1,2,1,1],
|
||||
[1,2,2,2,1,1,4,1,1,1,1,2,1,1],
|
||||
[1,2,4,2,2,1,5,4,1,5,1,2,1,1],
|
||||
[1,2,1,2,1,4,1,5,1,1,2,2,1,1],
|
||||
[1,2,5,1,6,5,1,4,1,1,1,2,1,1],
|
||||
[1,2,1,1,1,4,1,4,1,5,1,2,1,1],
|
||||
[1,2,2,2,2,2,2,2,2,2,2,2,1,1],
|
||||
[1,1,1,1,1,1,1,1,1,1,1,1,1,1]
|
||||
]
|
||||
|
||||
aLevel = aLevel1
|
||||
nActiveLevel = 1
|
||||
|
||||
# For Game Restart
|
||||
aLevel1Copy = aLevel1
|
||||
aLevel2Copy = aLevel2
|
||||
aPlayerCopy = aPlayer
|
||||
|
||||
C_LEVEL_ROWSCOUNT = 10
|
||||
C_LEVEL_COLSCOUNT = 14
|
||||
|
||||
C_EMPTY = 1
|
||||
C_WALL = 2
|
||||
C_PLAYER = 3
|
||||
C_DOOR = 4
|
||||
C_BOX = 5
|
||||
C_BOXONDOOR = 6
|
||||
C_PLAYERONDOOR = 7
|
||||
|
||||
nKeyClock = clock()
|
||||
|
||||
# Will be used when moving a Box
|
||||
aCurrentBox = [ :Row = 0, :Col = 0 ]
|
||||
nRowDiff = 0
|
||||
nColDiff = 0
|
||||
|
||||
# When the player win
|
||||
lPlayerWin = False
|
||||
|
||||
load "gameengine.ring"
|
||||
|
||||
func main
|
||||
|
||||
oGame = New Game
|
||||
{
|
||||
|
||||
title = "Sokoban"
|
||||
|
||||
Map {
|
||||
|
||||
blockwidth = 60
|
||||
blockheight = 60
|
||||
|
||||
aMap = aLevel
|
||||
|
||||
aImages = [
|
||||
"images/empty.jpg",
|
||||
"images/wall.jpg",
|
||||
"images/player.jpg",
|
||||
"images/door.jpg",
|
||||
"images/box.jpg",
|
||||
"images/boxondoor.jpg",
|
||||
"images/player.jpg" # Player on Door
|
||||
]
|
||||
|
||||
keypress = func oGame,oSelf,nkey {
|
||||
# Avoid getting many keys in short time
|
||||
if (clock() - nKeyClock) < clockspersecond()/4 return ok
|
||||
nKeyClock = Clock()
|
||||
Switch nkey
|
||||
on Key_Esc
|
||||
oGame.Shutdown()
|
||||
on Key_Space
|
||||
# Restart the Level
|
||||
if nActiveLevel = 1
|
||||
aLevel = aLevel1Copy
|
||||
else
|
||||
aLevel = aLevel2Copy
|
||||
ok
|
||||
aPlayer = aPlayerCopy
|
||||
UpdateGameMap(oGame)
|
||||
lPlayerWin = False
|
||||
on Key_Right
|
||||
if aPlayer[:col] < C_LEVEL_COLSCOUNT
|
||||
nRowDiff = 0 nColDiff = 1
|
||||
MoveObject(oGame,PlayerType(),aPlayer[:row],aPlayer[:col]+1)
|
||||
ok
|
||||
on Key_Left
|
||||
if aPlayer[:col] > 1
|
||||
nRowDiff = 0 nColDiff = -1
|
||||
MoveObject(oGame,PlayerType(),aPlayer[:row],aPlayer[:col]-1)
|
||||
ok
|
||||
on Key_Up
|
||||
if aPlayer[:row] > 1
|
||||
nRowDiff = -1 nColDiff = 0
|
||||
MoveObject(oGame,PlayerType(),aPlayer[:row]-1,aPlayer[:col])
|
||||
ok
|
||||
on Key_Down
|
||||
if aPlayer[:row] < C_LEVEL_ROWSCOUNT
|
||||
nRowDiff = 1 nColDiff = 0
|
||||
MoveObject(oGame,PlayerType(),aPlayer[:row]+1,aPlayer[:col])
|
||||
ok
|
||||
off
|
||||
if lPlayerWin = False
|
||||
if CheckWin()
|
||||
lPlayerWin = True
|
||||
DisplayYouWin(oGame)
|
||||
ok
|
||||
ok
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
text {
|
||||
x = 70 y=550
|
||||
animate = false
|
||||
size = 20
|
||||
file = "fonts/pirulen.ttf"
|
||||
text = "Level:"
|
||||
color = rgb(0,0,0)
|
||||
}
|
||||
NewButton(oGame,180,550,150,30,"Level 1",:Click1)
|
||||
NewButton(oGame,350,550,150,30,"Level 2",:Click2)
|
||||
}
|
||||
|
||||
func MoveObject oGame,nObjectType,nNewRow,nNewCol
|
||||
lMove = False
|
||||
switch nObjectType
|
||||
on C_PLAYER
|
||||
switch aLevel[nNewRow][nNewCol]
|
||||
on C_EMPTY
|
||||
aLevel[aPlayer[:row]][aPlayer[:col]] = C_EMPTY
|
||||
aLevel[nNewRow][nNewCol] = C_PLAYER
|
||||
UpdateGameMap(oGame)
|
||||
aPlayer[:row] = nNewRow
|
||||
aPlayer[:col] = nNewCol
|
||||
lMove = True
|
||||
on C_DOOR
|
||||
aLevel[aPlayer[:row]][aPlayer[:col]] = C_EMPTY
|
||||
aLevel[nNewRow][nNewCol] = C_PLAYERONDOOR
|
||||
UpdateGameMap(oGame)
|
||||
aPlayer[:row] = nNewRow
|
||||
aPlayer[:col] = nNewCol
|
||||
lMove = True
|
||||
on C_BOX
|
||||
aCurrentBox[:row] = nNewRow
|
||||
aCurrentBox[:col] = nNewCol
|
||||
if MoveObject(oGame,C_BOX,nNewRow+nRowDiff,nNewCol+nColDiff)
|
||||
aLevel[aPlayer[:row]][aPlayer[:col]] = C_EMPTY
|
||||
aLevel[nNewRow][nNewCol] = C_PLAYER
|
||||
UpdateGameMap(oGame)
|
||||
aPlayer[:row] = nNewRow
|
||||
aPlayer[:col] = nNewCol
|
||||
lMove = True
|
||||
ok
|
||||
on C_BOXONDOOR
|
||||
aCurrentBox[:row] = nNewRow
|
||||
aCurrentBox[:col] = nNewCol
|
||||
if MoveObject(oGame,C_BOXONDOOR,nNewRow+nRowDiff,nNewCol+nColDiff)
|
||||
aLevel[aPlayer[:row]][aPlayer[:col]] = C_EMPTY
|
||||
aLevel[nNewRow][nNewCol] = C_PLAYERONDOOR
|
||||
UpdateGameMap(oGame)
|
||||
aPlayer[:row] = nNewRow
|
||||
aPlayer[:col] = nNewCol
|
||||
lMove = True
|
||||
ok
|
||||
off
|
||||
on C_PLAYERONDOOR
|
||||
switch aLevel[nNewRow][nNewCol]
|
||||
on C_EMPTY
|
||||
aLevel[aPlayer[:row]][aPlayer[:col]] = C_DOOR
|
||||
aLevel[nNewRow][nNewCol] = C_PLAYER
|
||||
UpdateGameMap(oGame)
|
||||
aPlayer[:row] = nNewRow
|
||||
aPlayer[:col] = nNewCol
|
||||
lMove = True
|
||||
on C_DOOR
|
||||
aLevel[aPlayer[:row]][aPlayer[:col]] = C_DOOR
|
||||
aLevel[nNewRow][nNewCol] = C_PLAYERONDOOR
|
||||
UpdateGameMap(oGame)
|
||||
aPlayer[:row] = nNewRow
|
||||
aPlayer[:col] = nNewCol
|
||||
lMove = True
|
||||
on C_BOX
|
||||
aCurrentBox[:row] = nNewRow
|
||||
aCurrentBox[:col] = nNewCol
|
||||
if MoveObject(oGame,C_BOX,nNewRow+nRowDiff,nNewCol+nColDiff)
|
||||
aLevel[aPlayer[:row]][aPlayer[:col]] = C_DOOR
|
||||
aLevel[nNewRow][nNewCol] = C_PLAYER
|
||||
UpdateGameMap(oGame)
|
||||
aPlayer[:row] = nNewRow
|
||||
aPlayer[:col] = nNewCol
|
||||
lMove = True
|
||||
ok
|
||||
on C_BOXONDOOR
|
||||
aCurrentBox[:row] = nNewRow
|
||||
aCurrentBox[:col] = nNewCol
|
||||
if MoveObject(oGame,C_BOXONDOOR,nNewRow+nRowDiff,nNewCol+nColDiff)
|
||||
aLevel[aPlayer[:row]][aPlayer[:col]] = C_DOOR
|
||||
aLevel[nNewRow][nNewCol] = C_PLAYER
|
||||
UpdateGameMap(oGame)
|
||||
aPlayer[:row] = nNewRow
|
||||
aPlayer[:col] = nNewCol
|
||||
lMove = True
|
||||
ok
|
||||
off
|
||||
on C_BOX
|
||||
switch aLevel[nNewRow][nNewCol]
|
||||
on C_EMPTY
|
||||
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_EMPTY
|
||||
aLevel[nNewRow][nNewCol] = C_BOX
|
||||
UpdateGameMap(oGame)
|
||||
lMove = True
|
||||
on C_DOOR
|
||||
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_EMPTY
|
||||
aLevel[nNewRow][nNewCol] = C_BOXONDOOR
|
||||
UpdateGameMap(oGame)
|
||||
lMove = True
|
||||
on C_BOX
|
||||
aOldBox = aCurrentBox
|
||||
aCurrentBox[:row] = nNewRow
|
||||
aCurrentBox[:col] = nNewCol
|
||||
if MoveObject(oGame,C_BOX,nNewRow+nRowDiff,nNewCol+nColDiff)
|
||||
aCurrentBox = aOldBox
|
||||
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_EMPTY
|
||||
aLevel[nNewRow][nNewCol] = C_BOX
|
||||
UpdateGameMap(oGame)
|
||||
lMove = True
|
||||
ok
|
||||
on C_BOXONDOOR
|
||||
aOldBox = aCurrentBox
|
||||
aCurrentBox[:row] = nNewRow
|
||||
aCurrentBox[:col] = nNewCol
|
||||
if MoveObject(oGame,C_BOXONDOOR,nNewRow+nRowDiff,nNewCol+nColDiff)
|
||||
aCurrentBox = aOldBox
|
||||
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_EMPTY
|
||||
aLevel[nNewRow][nNewCol] = C_BOXONDOOR
|
||||
UpdateGameMap(oGame)
|
||||
lMove = True
|
||||
ok
|
||||
off
|
||||
on C_BOXONDOOR
|
||||
switch aLevel[nNewRow][nNewCol]
|
||||
on C_EMPTY
|
||||
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_DOOR
|
||||
aLevel[nNewRow][nNewCol] = C_BOX
|
||||
UpdateGameMap(oGame)
|
||||
lMove = True
|
||||
on C_DOOR
|
||||
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_DOOR
|
||||
aLevel[nNewRow][nNewCol] = C_BOXONDOOR
|
||||
UpdateGameMap(oGame)
|
||||
lMove = True
|
||||
on C_BOX
|
||||
aOldBox = aCurrentBox
|
||||
aCurrentBox[:row] = nNewRow
|
||||
aCurrentBox[:col] = nNewCol
|
||||
if MoveObject(oGame,C_BOX,nNewRow+nRowDiff,nNewCol+nColDiff)
|
||||
aCurrentBox = aOldBox
|
||||
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_DOOR
|
||||
aLevel[nNewRow][nNewCol] = C_BOX
|
||||
UpdateGameMap(oGame)
|
||||
lMove = True
|
||||
ok
|
||||
on C_BOXONDOOR
|
||||
aOldBox = aCurrentBox
|
||||
aCurrentBox[:row] = nNewRow
|
||||
aCurrentBox[:col] = nNewCol
|
||||
if MoveObject(oGame,C_BOXONDOOR,nNewRow+nRowDiff,nNewCol+nColDiff)
|
||||
aCurrentBox = aOldBox
|
||||
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_DOOR
|
||||
aLevel[nNewRow][nNewCol] = C_BOXONDOOR
|
||||
UpdateGameMap(oGame)
|
||||
lMove = True
|
||||
ok
|
||||
|
||||
off
|
||||
off
|
||||
return lMove
|
||||
|
||||
func UpdateGameMap oGame
|
||||
# The Map is our first object in Game Objects
|
||||
oGame.aObjects[1].aMap = aLevel
|
||||
|
||||
func PlayerType
|
||||
# It could be (Player) or (Player on door)
|
||||
return aLevel[aPlayer[:row]][aPlayer[:col]]
|
||||
|
||||
func CheckWin
|
||||
for aRow in aLevel
|
||||
if find(aRow,C_DOOR) or find(aRow,C_PLAYERONDOOR)
|
||||
return False
|
||||
ok
|
||||
next
|
||||
return True
|
||||
|
||||
func DisplayYouWin oGame
|
||||
oGame {
|
||||
text {
|
||||
point = 400
|
||||
size = 30
|
||||
nStep = 9
|
||||
file = "fonts/pirulen.ttf"
|
||||
text = "You Win !!!"
|
||||
x = 500 y=10
|
||||
state = func ogame,oself {
|
||||
if oself.y >= 400
|
||||
ogame.remove(oSelf.nIndex)
|
||||
ok
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewButton oGame,nX,nY,nWidth,nHeight,cText,cFunc
|
||||
oGame {
|
||||
Object {
|
||||
x = nX y=nY width = nWidth height=nHeight
|
||||
AddAttribute(self,:Text)
|
||||
AddAttribute(self,:EventCode)
|
||||
Text = cText
|
||||
EventCode = cFunc
|
||||
draw = func oGame,oSelf {
|
||||
oSelf {
|
||||
gl_draw_filled_rectangle(x,y,x+width,y+height,gl_map_rgb(0,100,255))
|
||||
gl_draw_rectangle(x,y,x+width,y+height,gl_map_rgb(0,0,0),2)
|
||||
oFont = oResources.LoadFont("fonts/pirulen.ttf",20)
|
||||
gl_draw_text(oFont,gl_map_rgb(0,0,0),x+width/2,y+5,1,Text)
|
||||
}
|
||||
}
|
||||
mouse = func oGame,oSelf,nType,aMouseList {
|
||||
if nType = GE_MOUSE_UP
|
||||
MouseX = aMouseList[GE_MOUSE_X]
|
||||
MouseY = aMouseList[GE_MOUSE_Y]
|
||||
oSelf {
|
||||
if MouseX >= x and MouseX <= X+270 and
|
||||
MouseY >= y and MouseY <= Y+40
|
||||
call EventCode(oGame,oSelf)
|
||||
ok
|
||||
}
|
||||
ok
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(oGame.aObjects)
|
||||
|
||||
func Click1 oGame,oSelf
|
||||
aLevel = aLevel1
|
||||
nActiveLevel = 1
|
||||
aPlayer = aPlayerCopy
|
||||
UpdateGameMap(oGame)
|
||||
lPlayerWin = False
|
||||
|
||||
func Click2 oGame,oSelf
|
||||
aLevel = aLevel2
|
||||
nActiveLevel = 2
|
||||
aPlayer = aPlayerCopy
|
||||
UpdateGameMap(oGame)
|
||||
lPlayerWin = False
|
||||
59
Task/Sokoban/Ruby/sokoban-1.rb
Normal file
59
Task/Sokoban/Ruby/sokoban-1.rb
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
require 'set'
|
||||
|
||||
class Sokoban
|
||||
def initialize(level)
|
||||
board = level.each_line.map(&:rstrip)
|
||||
@nrows = board.map(&:size).max
|
||||
board.map!{|line| line.ljust(@nrows)}
|
||||
board.each_with_index do |row, r|
|
||||
row.each_char.with_index do |ch, c|
|
||||
@px, @py = c, r if ch == '@' or ch == '+'
|
||||
end
|
||||
end
|
||||
@goal = board.join.tr(' .@#$+*', ' . ..')
|
||||
.each_char.with_index.select{|ch, c| ch == '.'}
|
||||
.map(&:last)
|
||||
@board = board.join.tr(' .@#$+*', ' @#$ $')
|
||||
end
|
||||
|
||||
def pos(x, y)
|
||||
y * @nrows + x
|
||||
end
|
||||
|
||||
def push(x, y, dx, dy, board) # modify board
|
||||
return if board[pos(x+2*dx, y+2*dy)] != ' '
|
||||
board[pos(x , y )] = ' '
|
||||
board[pos(x + dx, y + dy)] = '@'
|
||||
board[pos(x+2*dx, y+2*dy)] = '$'
|
||||
end
|
||||
|
||||
def solved?(board)
|
||||
@goal.all?{|i| board[i] == '$'}
|
||||
end
|
||||
|
||||
DIRS = [[0, -1, 'u', 'U'], [ 1, 0, 'r', 'R'], [0, 1, 'd', 'D'], [-1, 0, 'l', 'L']]
|
||||
def solve
|
||||
queue = [[@board, "", @px, @py]]
|
||||
visited = Set[@board]
|
||||
|
||||
until queue.empty?
|
||||
current, csol, x, y = queue.shift
|
||||
|
||||
for dx, dy, cmove, cpush in DIRS
|
||||
work = current.dup
|
||||
case work[pos(x+dx, y+dy)] # next character
|
||||
when '$'
|
||||
next unless push(x, y, dx, dy, work)
|
||||
next unless visited.add?(work)
|
||||
return csol+cpush if solved?(work)
|
||||
queue << [work, csol+cpush, x+dx, y+dy]
|
||||
when ' '
|
||||
work[pos(x, y)] = ' '
|
||||
work[pos(x+dx, y+dy)] = '@'
|
||||
queue << [work, csol+cmove, x+dx, y+dy] if visited.add?(work)
|
||||
end
|
||||
end
|
||||
end
|
||||
"No solution"
|
||||
end
|
||||
end
|
||||
11
Task/Sokoban/Ruby/sokoban-2.rb
Normal file
11
Task/Sokoban/Ruby/sokoban-2.rb
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
level = <<EOS
|
||||
#######
|
||||
# #
|
||||
# #
|
||||
#. # #
|
||||
#. $$ #
|
||||
#.$$ #
|
||||
#.# @#
|
||||
#######
|
||||
EOS
|
||||
puts level, "", Sokoban.new(level).solve
|
||||
95
Task/Sokoban/Ruby/sokoban-3.rb
Normal file
95
Task/Sokoban/Ruby/sokoban-3.rb
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
class Sokoban
|
||||
def initialize(level)
|
||||
board = level.lines.map(&:rstrip)
|
||||
leng = board.map(&:length).max
|
||||
board = board.map{|line| line.ljust(leng)}.join
|
||||
@goal = []
|
||||
board.each_char.with_index do |c, i|
|
||||
@player = i if c == '@' or c == '+'
|
||||
@goal << i if c == '.' or c == '+' or c == '*'
|
||||
end
|
||||
@board = board.tr(' .@#$+*', ' @#$ $')
|
||||
@lurd = [[-1, 'l', 'L'], [-leng, 'u', 'U'], [1, 'r', 'R'], [leng, 'd', 'D']]
|
||||
@dirs = @lurd.map(&:first)
|
||||
set_dead_zone(board.tr('^#', ' '))
|
||||
end
|
||||
|
||||
def set_dead_zone(wall)
|
||||
corner = search_corner(wall)
|
||||
@dead = corner.dup
|
||||
begin
|
||||
size = @dead.size
|
||||
corner.each do |pos|
|
||||
@dirs.each do |dir|
|
||||
next if wall[pos + dir] == '#'
|
||||
@dead.concat(check_side(wall, pos+dir, dir))
|
||||
end
|
||||
end
|
||||
end until size == @dead.size
|
||||
end
|
||||
|
||||
def search_corner(wall)
|
||||
wall.size.times.with_object([]) do |i, corner|
|
||||
next if wall[i] == '#' or @goal.include?(i)
|
||||
case count_wall(wall, i)
|
||||
when 2
|
||||
corner << i if wall[i-1] != wall[i+1]
|
||||
when 3
|
||||
corner << i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_side(wall, pos, dir)
|
||||
wk = []
|
||||
until wall[pos] == '#' or count_wall(wall, pos) == 0 or @goal.include?(pos)
|
||||
return wk if @dead.include?(pos)
|
||||
wk << pos
|
||||
pos += dir
|
||||
end
|
||||
[]
|
||||
end
|
||||
|
||||
def count_wall(wall, pos)
|
||||
@dirs.count{|dir| wall[pos + dir] == '#'}
|
||||
end
|
||||
|
||||
def push_box(pos, dir, board)
|
||||
return board if board[pos + 2*dir] != ' '
|
||||
board[pos ] = ' '
|
||||
board[pos + dir] = '@'
|
||||
board[pos + 2*dir] = '$'
|
||||
board
|
||||
end
|
||||
|
||||
def solved?(board)
|
||||
@goal.all?{|i| board[i] == '$'}
|
||||
end
|
||||
|
||||
def solve
|
||||
queue = [[@board, "", @player]]
|
||||
# When the key doesn't exist in Hash, it subscribes a key but it returns false.
|
||||
visited = Hash.new{|h,k| h[k]=true; false}
|
||||
visited[@board] # first subscription
|
||||
|
||||
until queue.empty?
|
||||
board, route, pos = queue.shift
|
||||
@lurd.each do |dir, move, push|
|
||||
work = board.dup
|
||||
case work[pos+dir]
|
||||
when '$' # push
|
||||
work = push_box(pos, dir, work)
|
||||
next if visited[work]
|
||||
return route+push if solved?(work)
|
||||
queue << [work, route+push, pos+dir] unless @dead.include?(pos+2*dir)
|
||||
when ' ' # move
|
||||
work[pos ] = ' '
|
||||
work[pos+dir] = '@'
|
||||
next if visited[work]
|
||||
queue << [work, route+move, pos+dir]
|
||||
end
|
||||
end
|
||||
end
|
||||
"No solution"
|
||||
end
|
||||
end
|
||||
70
Task/Sokoban/Tcl/sokoban-1.tcl
Normal file
70
Task/Sokoban/Tcl/sokoban-1.tcl
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package require Tcl 8.5
|
||||
|
||||
proc solveSokoban b {
|
||||
set cols [string length [lindex $b 0]]
|
||||
set dxes [list [expr {-$cols}] $cols -1 1]
|
||||
set i 0
|
||||
foreach c [split [join $b ""] ""] {
|
||||
switch $c {
|
||||
" " {lappend bdc " "}
|
||||
"#" {lappend bdc "#"}
|
||||
"@" {lappend bdc " ";set startplayer $i }
|
||||
"$" {lappend bdc " ";lappend startbox $i}
|
||||
"." {lappend bdc " "; lappend targets $i}
|
||||
"+" {lappend bdc " ";set startplayer $i; lappend targets $i}
|
||||
"*" {lappend bdc " ";lappend startbox $i;lappend targets $i}
|
||||
}
|
||||
incr i
|
||||
}
|
||||
set q [list [list $startplayer $startbox] {}]
|
||||
set store([lindex $q 0]) {}
|
||||
for {set idx 0} {$idx < [llength $q]} {incr idx 2} {
|
||||
lassign [lindex $q $idx] x boxes
|
||||
foreach dir {U D L R} dx $dxes {
|
||||
if {[set x1 [expr {$x + $dx}]] in $boxes} {
|
||||
if {[lindex $bdc [incr x1 $dx]] ne " " || $x1 in $boxes} {
|
||||
continue
|
||||
}
|
||||
set tmpboxes $boxes
|
||||
set x1 [expr {$x + $dx}]
|
||||
for {set i 0} {$i < [llength $boxes]} {incr i} {
|
||||
if {[lindex $boxes $i] == $x1} {
|
||||
lset tmpboxes $i [expr {$x1 + $dx}]
|
||||
break
|
||||
}
|
||||
}
|
||||
if {$dx == 1 || $dx == -1} {
|
||||
set next [list $x1 $tmpboxes]
|
||||
} else {
|
||||
set next [list $x1 [lsort -integer $tmpboxes]]
|
||||
}
|
||||
if {![info exists store($next)]} {
|
||||
if {$targets eq [lindex $next 1]} {
|
||||
foreach c [lindex $q [expr {$idx + 1}]] {
|
||||
lassign $c ispush olddir
|
||||
if {$ispush} {
|
||||
append solution $olddir
|
||||
} else {
|
||||
append solution [string tolower $olddir]
|
||||
}
|
||||
}
|
||||
return [append solution $dir]
|
||||
}
|
||||
set store($next) {}
|
||||
set nm [lindex $q [expr {$idx + 1}]]
|
||||
lappend q $next
|
||||
lappend q [lappend nm [list 1 $dir]]
|
||||
}
|
||||
} elseif {[lindex $bdc $x1] eq " "} {
|
||||
set next [list [expr {$x + $dx}] $boxes]
|
||||
if {![info exists store($next)]} {
|
||||
set store($next) {}
|
||||
set nm [lindex $q [expr {$idx + 1}]]
|
||||
lappend q $next
|
||||
lappend q [lappend nm [list 0 $dir]]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
error "no solution"
|
||||
}
|
||||
11
Task/Sokoban/Tcl/sokoban-2.tcl
Normal file
11
Task/Sokoban/Tcl/sokoban-2.tcl
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
set level {
|
||||
"#######"
|
||||
"# #"
|
||||
"# #"
|
||||
"#. # #"
|
||||
"#. $$ #"
|
||||
"#.$$ #"
|
||||
"#.# @#"
|
||||
"#######"
|
||||
}
|
||||
puts [solveSokoban $level]
|
||||
107
Task/Sokoban/Wren/sokoban.wren
Normal file
107
Task/Sokoban/Wren/sokoban.wren
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import "/dynamic" for Tuple
|
||||
import "/llist" for DLinkedList
|
||||
import "/set" for Set
|
||||
|
||||
var Board = Tuple.create("Board", ["cur", "sol", "x", "y"])
|
||||
|
||||
class Sokoban {
|
||||
construct new(board) {
|
||||
_destBoard = ""
|
||||
_currBoard = ""
|
||||
_nCols = board[0].count
|
||||
_playerX = 0
|
||||
_playerY = 0
|
||||
for (r in 0...board.count) {
|
||||
for (c in 0..._nCols) {
|
||||
var ch = board[r][c]
|
||||
_destBoard = _destBoard + ((ch != "$" && ch != "@") ? ch : " ")
|
||||
_currBoard = _currBoard + ((ch != ".") ? ch : " ")
|
||||
if (ch == "@") {
|
||||
_playerX = c
|
||||
_playerY = r
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
move(x, y, dx, dy, trialBoard) {
|
||||
var newPlayerPos = (y + dy) * _nCols + x + dx
|
||||
if (trialBoard[newPlayerPos] != " ") return ""
|
||||
var trial = trialBoard.toList
|
||||
trial[y * _nCols + x] = " "
|
||||
trial[newPlayerPos] = "@"
|
||||
return trial.join()
|
||||
}
|
||||
|
||||
push(x, y, dx, dy, trialBoard) {
|
||||
var newBoxPos = (y + 2 * dy) * _nCols + x + 2 * dx
|
||||
if (trialBoard[newBoxPos] != " ") return ""
|
||||
var trial = trialBoard.toList
|
||||
trial[y * _nCols + x] = " "
|
||||
trial[(y + dy) * _nCols + x + dx] = "@"
|
||||
trial[newBoxPos] = "$"
|
||||
return trial.join("")
|
||||
}
|
||||
|
||||
isSolved(trialBoard) {
|
||||
for (i in 0...trialBoard.count) {
|
||||
if ((_destBoard[i] == ".") != (trialBoard[i] == "$")) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
solve() {
|
||||
var dirLabels = [ ["u", "U"], ["r", "R"], ["d", "D"], ["l", "L"] ]
|
||||
var dirs = [ [0, -1], [1, 0], [0, 1], [-1, 0] ]
|
||||
var history = Set.new()
|
||||
history.add(_currBoard)
|
||||
var open = DLinkedList.new()
|
||||
open.add(Board.new(_currBoard, "", _playerX, _playerY))
|
||||
|
||||
while (!open.isEmpty) {
|
||||
var b = open.removeAt(0)
|
||||
for (i in 0...dirs.count) {
|
||||
var trial = b.cur
|
||||
var dx = dirs[i][0]
|
||||
var dy = dirs[i][1]
|
||||
|
||||
// are we standing next to a box ?
|
||||
if (trial[(b.y + dy) * _nCols + b.x + dx] == "$") {
|
||||
// can we push it ?
|
||||
trial = push(b.x, b.y, dx, dy, trial)
|
||||
if (!trial.isEmpty) {
|
||||
// or did we already try this one ?
|
||||
if (!history.contains(trial)) {
|
||||
var newSol = b.sol + dirLabels[i][1]
|
||||
if (isSolved(trial)) return newSol
|
||||
open.add(Board.new(trial, newSol, b.x + dx, b.y + dy))
|
||||
history.add(trial)
|
||||
}
|
||||
}
|
||||
} else { // otherwise try changing position
|
||||
trial = move(b.x, b.y, dx, dy, trial)
|
||||
if (!trial.isEmpty && !history.contains(trial)) {
|
||||
var newSol = b.sol + dirLabels[i][0]
|
||||
open.add(Board.new(trial, newSol, b.x + dx, b.y + dy))
|
||||
history.add(trial)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "No solution"
|
||||
}
|
||||
}
|
||||
|
||||
var level = [
|
||||
"#######",
|
||||
"# #",
|
||||
"# #",
|
||||
"#. # #",
|
||||
"#. $$ #",
|
||||
"#.$$ #",
|
||||
"#.# @#",
|
||||
"#######"
|
||||
]
|
||||
System.print(level.join("\n"))
|
||||
System.print()
|
||||
System.print(Sokoban.new(level).solve())
|
||||
Loading…
Add table
Add a link
Reference in a new issue