Compare commits

...

4 Commits

4
.gitignore vendored

@ -12,4 +12,6 @@ htmlcov/
dist/
build/
*.egg-info/
*.egg-info/
puzzles

@ -0,0 +1,53 @@
# Prototype for a chaotic evil jigsaw puzzles system
This is a rough prototype for generating ready-to-print jigsaw puzzles as well as a way to track their completion in a shared platform.
The idea is to have several puzzles and mix their pieces, in a way that invites the players to collaborate in order to solve them.
This prototype covers two aspects of the process: the first is to split an image into pieces, tracking every piece with an ID and store the relation between adjacent tiles. The second concerns the online platform, and it is a Flask application that permits to upload cluster of pieces in order to share them with the other players and unlocking the full puzzle.
## To install the project:
1. Clone the repo
2. Create a virtual environment
```
$ python3 -m venv venv
```
3. Activate the virtual environment
```
$ . venv/bin/activate
```
4. Install the dependencies
```
$ pip install -e .
```
5. Set the environmental variables for flask
```
$ export FLASK_APP=flaskr
$ export FLASK_ENV=development
$ flask run
```
6. The Flask application will be accessible on your browser at `localhost:5000`. If you try to navigate there you will see a blank page. This is because we need to generate some puzzles to display.
## Generating the contents
The first thing to do then is to run the `split.py` script:
```
python3 chaospuzzles/split.py
```
This will take the Katamari demo picture from `static/img` and will split it in tiles. The tiles will be used to compose the clusters when a player upload it online. In this way we can be super flexible in the randomization / distribuition of the pieces.
You can tweak the parameters at the end of the split.py file. This is temporary, later on it would be nice to have an interface to prepare the puzzle.
## Completing the puzzles
If you reload the website, you will see a link pointing to the Katamari page. Here we will find an empty frame, and a form to insert new pieces.
Try it! You can find the IDs for the pieces in the `chaospuzzles/puzzles/katamari/katamari_retro.png` image. This is the picture generated in order to be printed behind the puzzle and let every piece to have a unique ID.
By design an valid cluster is a group of adjacent pieces.
Keep in mind that this is a wip and rough prototype so everything need to be polished a lot. We count on your imagination to fill the lack of design, UI and UX here. Imagination is the best modern feature of 2022!
Thanks and see you sun ☀️

@ -41,7 +41,6 @@ def create_app(test_config=None):
from . import puzzle
app.register_blueprint(puzzle.bp)
# app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix="/soupboat/chaospuzzles")
app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix="/soupboat/chaospuzzles")
return app

@ -0,0 +1,63 @@
from wand.image import Image
def is_valid(adjacents, cluster):
valid = True
for p0 in cluster:
connection = []
for p1 in cluster:
if p0 == p1:
continue
connected = p0 in adjacents[p1]
connection.append(connected)
if not True in connection:
valid = False
print(f"Not valid fragment, piece {p0} is not connected")
break
print("The fragment is valid!")
return valid
def index2d(list2d, value):
return next(
(i, j) for i, lst in enumerate(list2d) for j, x in enumerate(lst) if x == value
)
def create(pieces, cluster, name, side):
schema = []
for ID in cluster:
piece = {"ID": ID, "coords": index2d(pieces, ID)}
schema.append(piece)
left = min(schema, key=lambda piece: piece["coords"][0])["coords"][0]
right = max(schema, key=lambda piece: piece["coords"][0])["coords"][0]
top = min(schema, key=lambda piece: piece["coords"][1])["coords"][1]
bottom = max(schema, key=lambda piece: piece["coords"][1])["coords"][1]
width = (right - left + 1) * side
height = (bottom - top + 1) * side
print(f"Final cluster will be {width} x {height} px")
with Image(width=width, height=height) as img:
cluster_name = "cluster"
for piece in schema:
cluster_name = f"{cluster_name}-{piece['ID']}"
coords = piece["coords"]
with Image(
width=side,
height=side,
filename=f"chaospuzzles/puzzles/{name}/pieces/{name}-{coords[0]}-{coords[1]}.jpg",
) as tile:
l = (piece["coords"][0] - left) * side
t = (piece["coords"][1] - top) * side
img.composite(tile, left=l, top=t)
print(f"Composed with tile {piece['ID']}, at ({l}, {t})")
# img.save(filename=f"chaospuzzles/puzzles/{name}/clusters/{cluster_name}.png")
img.save(filename=f"chaospuzzles/static/img/{name}-{cluster_name}.png")
print("Done!")
return {
"name": f"{cluster_name}.png",
"origin": (left, top),
"size": (right - left + 1, bottom - top + 1),
}

@ -1,7 +1,7 @@
import os
import json
from flask import Blueprint, render_template
from flask import Blueprint, redirect, render_template, request
from . import cluster
bp = Blueprint("puzzle", __name__, url_prefix="/")
@ -25,11 +25,32 @@ def home():
return render_template("home.html", puzzles=puzzles)
@bp.route("/<puzzle>")
@bp.route("/<puzzle>", methods=("GET", "POST"))
def puzzle(puzzle=None):
with open(f"{root}/puzzles/{puzzle}/info.json") as data:
info = json.load(data)
if request.method == "POST":
fragments = request.form.getlist("pieces")
with open(f"{root}/puzzles/{puzzle}/adjacents.json") as adj_data:
adjacents = json.load(adj_data)
valid = cluster.is_valid(adjacents, fragments)
if valid:
with open(f"{root}/puzzles/{puzzle}/pieces.json") as pieces_data:
pieces = json.load(pieces_data)
c = cluster.create(
pieces, fragments, puzzle, int(info["width"] / info["rows"])
)
with open(f"{root}/puzzles/{puzzle}/info.json", "w") as data:
info["clusters"].append(c)
data.write(json.dumps(info))
redirect(f"/{puzzle}")
# cringe sorry
if info["width"] >= info["height"]:
info["ratio"] = info["width"] / info["height"]
else:

@ -118,6 +118,7 @@ def split(image, width, height, pieces):
"width": width,
"height": height,
"image": image_file,
"clusters": [],
}
)
)

@ -2,13 +2,41 @@
box-sizing: border-box;
}
.container {
position: relative;
}
.puzzle {
display: inline-block;
margin: 0 auto;
font-size: 0px;
border-collapse: collapse;
border: 1px solid springgreen;
}
.piece {
display: inline-block;
background-color: springgreen;
margin: 0;
}
.clusters {
width: 100vmin;
position: absolute;
top: 0;
left: 0;
display: inline-block;
margin: 0 auto;
}
.cluster {
position: absolute;
}
/* .reference {
position: absolute;
left: 0;
top: 0;
width: 100vmin;
height: auto;
opacity: 0.1;
} */

@ -0,0 +1,17 @@
const listElements = document.getElementsByClassName("list-input");
function insertAfter(referenceNode, newNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
Array.from(listElements).forEach((input) => {
let add = document.createElement("button");
console.log(input);
add.innerHTML = "+";
add.addEventListener("click", (e) => {
e.preventDefault();
cloneInput = input.cloneNode(true);
insertAfter(input, cloneInput);
});
insertAfter(input, add);
});

@ -6,19 +6,46 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{puzzle}}</title>
<link rel="stylesheet" href="{{url_for('static', filename='css/puzzle.css')}}">
<script src="{{url_for('static', filename='js/list.js')}}" defer></script>
</head>
<body>
<h1>{{puzzle}}</h1>
<div class="puzzle">
<div class="container">
<table class="puzzle">
{% for column in range(columns) %}
<tr>
{% for row in range(rows) %}
<td class="piece" style="width: {{ 100 / rows }}vmin; height: {{ 100 / ratio / columns }}vmin; "></td>
{%endfor%}
</tr>
{% endfor %}
</table>
<!-- <img src="{{url_for('static', filename='img/katamari.jpg')}}" alt="" class="reference"> -->
<div class="clusters">
{% for cluster in clusters %}
<img
class="cluster"
src="{{url_for('static', filename='img/' + puzzle + '-' + cluster['name'])}}"
style="
width: {{100/rows*cluster['size'][0]}}vmin;
height: {{ 100 / ratio / columns * cluster['size'][1] }}vmin;
left: {{100 / rows*cluster['origin'][0]}}vmin;
top: {{100/ratio/columns*cluster['origin'][1]}}vmin"
/>
{% endfor %}
</div>
{% for column in range(columns) %} {% for row in range(rows) %}
<div class="piece" style="width: {{ 100 / rows }}vmin; height: {{ 100 / ratio / columns }}vmin; "></div>
{%endfor%}
<br />
{% endfor %}
</div>
</div>
</div>
<form action="" method="post">
<input type="text" name="pieces" class="list-input">
<input type="submit">
</form>
</body>
</html>
</html>

@ -0,0 +1,77 @@
# inventory for the loot box puzzles
## Source materials
```
title contents + ready?
Experimental Kitchen Role Play for collective meetings ruleset card yes
Miriam the leader card game ruleset card yes
Fanfiction Katamarri text website yes
Fanfiction "Flirting on school" text yes
Fanfiction Nim Gersande text yes
1 sentence games text yes
"Connect less" Alex ruleset yes
alex drawings from the public lectures ? image yes
Glossary text kinda
Xquisite branch (tool) documentation website prototype
chameleon rulese ? ruleset prototype
Bitsy "From Player to Payer" (only draft) videogame draft
Bitsy "Unfinished Thoughts" draft from Supi and Jian videogame draft
Bitsy "friends into friends into friends.." em (only draft) videogame draft
Mimic Research text image draft
what is a loot box manifesto text draft
Crossword Glossary Booklet Emma text crowsswords idk
double maze Supi image? idk
Chaos Puzzle manga documentation text image no
Miriam's life hacks text ??? no
Karaoke "Gaming can make a Better World" text video no
Fanfiction Werwolf / Millers Hollow text wiki no
```
## puzzles inventory
```
title categories token to collection
Chaos Puzzle manga documentation text visual overview
What is a loot box - manifesto text research
1 sentence games text research
Glossary (could it be multiple puzzles?) text research
Alex drawings from public lectures ? visual research
Mimic Research text visual research
Experimental Kitchen Charachters game ruleset cards ruleset
Miriam the Leader game ruleset cards ruleset
Connect less - Alex game ruleset ruleset
Chameleon ruleset ? game ruleset website ruleset
xquisite branch - branched ruleset tool ruleset website ruleset
Fanfiction Katamarri text website fanfic
Fanfiction "Flirting on school" text fanfic
Fanfiction Nim Gersande text fanfic
Crossword Glossary Booklet Emma game visual
double maze Supi game visual
Miriam's life hacks text cards
Gaming can make a Better World text visual karaoke
From Player to Payer Jian visual videogame map
Unfinished Thoughts - Supi and Jian visual videogame map
friends into friends into friends - Em visual videogame map
```
Loading…
Cancel
Save