Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
d4c38a0
docs(deps): updated project version
f1i3g3 Nov 19, 2025
e6562aa
fix: updated Cargo.lock
f1i3g3 Nov 19, 2025
615643d
refactor: small fix
f1i3g3 Nov 19, 2025
379e770
style: added TODO for one test
f1i3g3 Nov 19, 2025
dc0f43e
refactor: changed project structure with demo
f1i3g3 Nov 24, 2025
f0c827f
refactor: moved behaviour to demo
f1i3g3 Nov 24, 2025
75a94b7
refactor: moved dyncamic objects definition to demo
f1i3g3 Nov 24, 2025
01ab4a5
refactor: moved get_visible_objects to demo (for now)
f1i3g3 Nov 24, 2025
21332d5
style: formatting
f1i3g3 Nov 24, 2025
94004e3
add: discrete tile movement for player
kinokotakenoko9 Nov 24, 2025
d13ad9b
feat: impl macos support
Friend-zva Nov 25, 2025
bae0d2c
feat: MVP of levels concept
Friend-zva Nov 25, 2025
99ed48b
add: update atlas & animation logic
kinokotakenoko9 Nov 25, 2025
23a120b
refactor: add movement to "Up", "Down", "Left", "Right"
RodionovMaxim05 Nov 25, 2025
3585314
feat: tile logic and a walk map
RodionovMaxim05 Nov 25, 2025
c521ccc
add: new sprites
kinokotakenoko9 Nov 25, 2025
a2f0bb4
add: movement animations
kinokotakenoko9 Nov 26, 2025
11c269c
docs & test: update legacy code
RodionovMaxim05 Nov 26, 2025
2948e05
feat: add box pushing logic
RodionovMaxim05 Nov 26, 2025
00249e4
fix: tile sizes
kinokotakenoko9 Nov 26, 2025
764a26e
chore: few things
kinokotakenoko9 Nov 26, 2025
0bdcc63
fix: tile alignment
kinokotakenoko9 Nov 26, 2025
43561fe
fix: wall sprite position
kinokotakenoko9 Nov 26, 2025
feaf366
add: prepush & postpush states
kinokotakenoko9 Nov 26, 2025
6d486d8
add: movement speedup
kinokotakenoko9 Nov 27, 2025
a146501
Merge branch 'game/setup' into game/feat/levels
Friend-zva Nov 27, 2025
f37cd7d
feat: several levels
Friend-zva Nov 27, 2025
ce4ae0a
feat: demo menu page
Friend-zva Nov 27, 2025
bcd44de
fix: draw for linux and toggle level
Friend-zva Nov 27, 2025
71b57dd
fix: gen menu and parse assets alphabet
Friend-zva Nov 27, 2025
94b7277
fix: render of objects
Friend-zva Nov 27, 2025
0d966b6
feat: box-on-target detection, disable fps logging!
ns-58 Nov 28, 2025
78d94c9
fix: change box color only after it become IDLE
ns-58 Nov 28, 2025
4174aab
fix: G tyles should have type target
ns-58 Nov 28, 2025
308a599
fix: macos support and point toggle menu
Friend-zva Nov 28, 2025
8408d2e
feat: gen and crop assets
Friend-zva Nov 28, 2025
37a890e
Merge branch 'game/feat/success_detection' into game/ref
Friend-zva Nov 28, 2025
b522ed0
fix: detect completed level
Friend-zva Nov 28, 2025
77e22cc
feat: menu-level-menu swithes
ns-58 Nov 28, 2025
4ecd932
feat: back to menu using <- + ->
ns-58 Nov 28, 2025
b52eb9b
style: formatting
Friend-zva Nov 28, 2025
322d112
fix: unify resolution [wip]
kinokotakenoko9 Nov 29, 2025
1540523
fix: animations
kinokotakenoko9 Nov 29, 2025
cf2f9b0
chore: polish
kinokotakenoko9 Nov 29, 2025
00c6d4c
fix: fmt
kinokotakenoko9 Nov 29, 2025
c91e386
chore: react to linter report
Friend-zva Nov 30, 2025
c6cfc2e
docs: update README and LICENSE
Friend-zva Dec 1, 2025
4ceae2b
chore: add demo play
Friend-zva Dec 1, 2025
9ff0ab9
refactor: files structure
RodionovMaxim05 Dec 1, 2025
dc4ed0e
refactor: split the make_step into subfunctions
RodionovMaxim05 Dec 1, 2025
446f04a
refactor: change dir name for levels
RodionovMaxim05 Dec 1, 2025
56bfad6
docs: add doc for all game functions
RodionovMaxim05 Dec 1, 2025
3885e1c
refactor: improve readability of main function
RodionovMaxim05 Dec 1, 2025
d826ea7
refactor: react to linter report
RodionovMaxim05 Dec 1, 2025
cdea6a2
fix: huge fix for rendering [by f1i3g3]
Friend-zva Dec 3, 2025
aafe9c1
test: support new logic
Friend-zva Dec 3, 2025
6114b20
fix: offset between box and pushing player
Friend-zva Dec 3, 2025
5adc872
test: basic for 'initiator.ts'
Friend-zva Dec 4, 2025
4390b6b
test: basic for 'behaviour.rs'
Friend-zva Dec 4, 2025
6439d11
style: formatting
Friend-zva Dec 4, 2025
e5145f8
test: state in engine
Friend-zva Dec 4, 2025
9c8b4ca
test: all funcs and ignore main
Friend-zva Dec 4, 2025
8dff566
chore: update demo
Friend-zva Dec 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
run: cargo install cargo-tarpaulin

- name: Generate coverage
run: cargo tarpaulin --verbose --workspace --out Lcov --output-dir ./coverage
run: cargo tarpaulin --verbose --workspace --out Lcov --output-dir ./coverage --exclude-files "game/src/main.rs"

- name: Upload coverage
uses: codecov/codecov-action@v3
Expand Down
11 changes: 10 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 3 additions & 18 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,3 @@
[package]
name = "ferari"
version = "0.1.0"
edition = "2021"
description = "Fast Engine for Rendering Axonometric Rust-based Isometry"
authors = ["Rodion Suvorov <rodion.suvorov.94@mail.ru>, Ilhom Kombaev <kombaev02@gmail.com>, Vyacheslav Kochergin <vyacheslav.kochergin1@gmail.com>, Dmitri Kuznetsov <dmitrvlkuznetsov@gmail.com>"]
license = "MIT"
repository = "https://github.com/suvorovrain/Ferari"
readme = "README.md"

[dependencies]
minifb = "0.28"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
image = "0.25"
crossbeam-channel = "0.5.15"

[dev-dependencies]
[workspace]
members = ["engine", "game"]
resolver = "2"
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2025 Rodion Suvorov, Ilhom Kombaev, Vyacheslav Kochergin, Dmitri Kuznetsov
Copyright (c) 2025 21pack

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
94 changes: 66 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,52 @@
# Ferari
![CI Status](https://github.com/suvorovrain/Ferari/actions/workflows/ci.yml/badge.svg)
# GoFerari

![CI Status](https://github.com/21pack/GoFerari/actions/workflows/ci.yml/badge.svg)

Game on [Ferari](https://github.com/suvorovrain/Ferari) (Fast Engine for Rendering Axonometric Rust-based Isometry).

Currently, x86_64 Linux and ARM64 macOS platforms are supported.

![demo](/demo.gif)

Fast Engine for Rendering Axonometric Rust-based Isometry.
## Description
An isometric engine that allows you to create simple games with static objects and mobs.
## Authors
* Rodion Suvorov. [GitHub](https://github.com/suvorovrain), [Contact](https://t.me/suvorovrain).
* Ilhom Kombaev. [GitHub](https://github.com/homka122), [Contact](https://t.me/homka122).
* Vyacheslav Kochergin. [GitHub](https://github.com/VyacheslavIurevich), [Contact](https://t.me/se4life).
* Dmitri Kuznetsov. [GitHub](https://github.com/f1i3g3), [Contact](https://t.me/f1i3g3).
## Platforms
Currently, x86_64 Linux and Windows platforms are supported

An isometric game in puzzle genre such as [Sokoban](https://en.wikipedia.org/wiki/Sokoban). The aim of the game is to get the boxes onto storage locations.

### Mechanics

* Player: You control the worker. Move up, down, left, or right.
* Boxes: Push the boxes, one at a time. You cannot pull them or push two boxes at once.
* Walls: Solid grey blocks form the immovable barriers. They confine your and boxes movement.
* Target Docks: Indented floor tiles mark the delivery targets where the boxes must be placed.
* Box States:
* Light Box: A box that is not yet on a target.
* Dark Box: A box that has been successfully pushed onto a target dock.

## Usage
* Download release archive from GitHub releases page
* Put your custom map information into `input.json` file in the root of unpacked archive, according format from example in the root of repository

### Install

* Download release archive from GitHub releases page.
* Clone and build this repo.

### Play

```shell
./play.sh
```

### Control

The player moves on a grid-based system with fixed-direction controls. This means each key consistently moves the character in one cardinal direction, regardless of the on-screen perspective.

* `WASD` or `arrow control`: movement;
* `<-`(`A`) + `->`(`D`): go to menu;
* `esc`: close game.

## Dependencies

### Linux

```shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
sudo apt install cargo
Expand All @@ -23,23 +55,29 @@ rustup component add rustfmt
rustup component add clippy
cargo install cargo-tarpaulin
```
## Development

### macOS

```shell
brew install rustup
rustup-init
```

## Development (Ferari)

* See [CONTRIBUTING.md](./CONTRIBUTING.md)
* Compile & run via `cargo run`
* Compile & run game via `cargo run -p game --release`
* View docs via `cargo doc` (use --document-private-items if you want)
* Format your code via `cargo fmt`
* Everything else - in CI

## Benchmark

### Machine info:
OS: Kubuntu 24.04
CPU: Intel Core i7-12700H
RAM: 16 GB
| Mobs Count | Average FPS |
|-------------|-------------|
| 500 | 40 |
| 5000 | 15 |
| 10000 | 10 |
| 100000 | 4 |
| 1000000 | 0.7 |
## Authors

* **Maxim Rodionov:** [GitHub](https://github.com/RodionovMaxim05), [Telegram](https://t.me/Maxoon22)
* **Dmitri Chirkov:** [GitHub](https://github.com/kinokotakenoko9), [Telegram](https://t.me/chdmitri)
* **Nikita Shchutskii:** [GitHub](https://github.com/ns-58), [Telegram](https://t.me/szcz00)
* **Vladimir Zaikin:** [GitHub](https://github.com/Friend-zva), [Telegram](https://t.me/vo_va_w)

## License

Distributed under the [MIT License](https://choosealicense.com/licenses/mit/). See [`LICENSE`](LICENSE) for more information.
109 changes: 109 additions & 0 deletions assets/assetsgen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from PIL import Image
import json


SIZE_TILE = 256


def create_scaled_atlas_alphabet(atlas_path, json_data, output_path, scale=4):
atlas = Image.open(atlas_path)
with open(json_data, "r") as f:
data_tiles = json.load(f)

tile_size = data_tiles["meta"]["tile_size"]
tile_size_new = tile_size * scale

width_new, height_new = SIZE_TILE * 4, SIZE_TILE * 9
atlas_new = Image.new("RGBA", (width_new, height_new))

tiles_data_new = {}
tiles_data_new["frames"] = {}
tiles_data_new["meta"] = {
"image": "atlas_x2.png",
"tile_size": SIZE_TILE,
"version": 1,
}

for i, (tile_name, coords) in enumerate(data_tiles["frames"].items()):
x, y, w, h = coords["x"], coords["y"], coords["w"], coords["h"]
tile = atlas.crop((x, y, x + w, y + h))

resized_tile = tile.resize(
(tile_size_new, tile_size_new), Image.Resampling.NEAREST
)

col, row = i % 4, i // 4
x_new = col * SIZE_TILE + 60
y_new = row * SIZE_TILE + 20

atlas_new.paste(resized_tile, (x_new, y_new))

tiles_data_new["frames"][tile_name] = {
"x": col * SIZE_TILE,
"y": row * SIZE_TILE,
"w": SIZE_TILE,
"h": SIZE_TILE,
}

atlas_new.save(output_path)

new_json_path = output_path.replace(".png", ".json")
with open(new_json_path, "w") as f:
json.dump(tiles_data_new, f, indent=2)


def create_atlas_ground(atlas_path, json_data, output_path):
atlas = Image.open(atlas_path)
with open(json_data, "r") as f:
data_tiles = json.load(f)

tile_size = data_tiles["meta"]["tile_size"]

width_new, height_new = tile_size * 4, tile_size * 2
atlas_new = Image.new("RGBA", (width_new, height_new))

tiles_data_new = {}
tiles_data_new["frames"] = {}
tiles_data_new["meta"] = {
"image": "atlas_x2.png",
"tile_size": SIZE_TILE,
"version": 1,
}

for i, (tile_name, coords) in enumerate(data_tiles["frames"].items()):
x, y, w, h = coords["x"], coords["y"], coords["w"], coords["h"]
tile = atlas.crop((x, y, x + w, y + h))

resized_tile = tile

col, row = i % 4, i // 4
x_new = col * SIZE_TILE
y_new = row * SIZE_TILE

tile_size_new = SIZE_TILE

if tile_name == "wall_tile":
x_new += 22
y_new -= 14
tile_size_new = tile_size - 48
resized_tile = tile.resize(
(tile_size_new, tile_size_new), Image.Resampling.NEAREST
)

atlas_new.paste(resized_tile, (x_new, y_new))

tiles_data_new["frames"][tile_name] = {
"x": col * SIZE_TILE,
"y": row * SIZE_TILE,
"w": SIZE_TILE,
"h": SIZE_TILE,
}

atlas_new.save(output_path)

new_json_path = output_path.replace(".png", ".json")
with open(new_json_path, "w") as f:
json.dump(tiles_data_new, f, indent=2)


create_atlas_ground("assets/atlas.png", "assets/atlas.json", "assets/atlas_x2.png")
Binary file not shown.
Loading