はしがき
こんにちは、掘江です!
変愚蛮怒の開発メンバーをやっています!
この記事は「皆も飛ぼうぜ!」的なメンバー募集記事です。1
もちろん開発そっちのけでゲーム本体を遊ぶだけでもOK!
変愚蛮怒が属する「ローグライク」そのものの記事はWikipediaをご覧下さい
この際プログラミング未経験でも、ゲームについて語り合うとか「それ以外のこと」で手伝って頂けるだけでも大歓迎です!
全く何も知らなくても大丈夫、気軽にDiscordまで遊びに来て下さいね!
募集要項
最先端のプログラミング技術が好きだったりキャッチアップしたい方!
以上です!
慣れてきたらもっと上流から実施するタスクもあります!
- ドメイン駆動設計
- 値オブジェクト&エンティティ
- Model/View/Controller/Service/Repository分離
- リファクタリング
- C++20
- テンプレート
- ほとんどのSTL (※コンパイラ依存)
- 通信&暗号化
- Jsoncに基づいた構造化設定ファイル
- マルチOS対応
- Windows
- macOS
- Linux (Ubuntuメイン、RedHat系も有り)
- Git
- GitHub Actions によるCI/CDパイプライン
- Git Flow に基づいたブランチファースト開発
- デザインパターン (以下は導入済の技術、今後も拡張予定)
- Singleton
- Enum Factory
- Visitor
- Strategy
重要事項
変愚蛮怒はあくまで非営利の同人ゲームです。
色んなパロディが節操なく大量にぶち込まれてカオスの権化状態です。
それを許容できる方だと楽しく開発できると思います。
変愚蛮怒の歴史
変愚蛮怒はLinuxカーネル (1990)やGPL (1989)よりも古く、従って世界有数の超古参ソフトウェアです。
詳細は省略しますがソースコードの初版は1987年で、この時はUMoriaという名称でした。
当時のソースコードもC++になって新たに書き直されています。オープンソースで今も保守されている同一名称のソフトウェアとしては世界最古に近いのではないでしょうか。
UMoriaからフォークしたのがAngbandで、1990年のことです。これも保守が続いており、UIのモダン化など遊びやすい工夫が施されています。但しC72 (ANSIによる標準化よりも前)の記法で書かれているので読み解くのは至難です。
かなり古いバージョンですがAngband 日本語版も一応あります。
更にZangbandが1994年にフォークしましたが、現在は開発・保守が停止しています。
これも旧版ですがZangband 日本語版はあるので、往年の雰囲気を楽しんでみるのも良いかと思います。
そして本題の変愚蛮怒が2000年にフォークし、このフォークは現在まで活発に保守されています。
2014年には変愚蛮怒から幻想蛮怒 (旧名:変愚蛮怒勝手版 主に東方要素色々追加)がフォークしました。
光あるところ
はい、残念ながら影もあります。
これを言わないと不公平というか、コードを見た時の期待値ギャップが大きすぎますからね。
私は影を消す作業に5年ほど費やしてきましたが、色々あって人手不足を痛感したので 「どなたか一緒にやりませんか!?」 というのが募集理由の1つです。
- GUIが80年代のまま
- 今でも16色表示がデフォルトです (ドット絵モードはあるがアニメーションはない)
- 流石にメニューバーは90年代レベルまで引き上げられました
- ViewとModelとControllerとServiceとRepositoryの密結合
- ずーっと疎結合化作業を行っていますが全く底は見えません
- 変数名から変数の意味を見出すことは困難です2
- 関数名から処理内容や副作用、特にグローバル変数の書き換え有無を予測することは困難です
- 1000行レベルのトランザクションスクリプトがまだいくらか残っています
- 大量のC72コード
- C++20となった今、変数の宣言と代入が無意味に分かれている箇所がまだ大量に残っています
- 作業の行き届いていないコードではポインタと参照が混在しています
- 流石にGNU拡張は一切ありません (GCC/MSVC/Clang/Apple Clangの全てでコンパイルできるコードだけが許可)
- 可読性ゼロの設定ファイル
- キーバインド関係など、筆者ですら何をやっているか分からないファイルが複数あります (例:PC-9801のGraph → PC/ATのAlt)
- Jsoncも、ドメイン仕様を理解するのにしばらくかかることは間違いないです
- コードベースとドメイン仕様
- そもそも全合計で約26万行あります (ステップ数は日々変わるので不明)
- 40年近い増改築の果てに、ここには書ききれないほど色んな要素が絡み合うゲームになりました
色々ありますが、そもそもがゲームなので楽しいことは間違いないです。
一緒に開発できる仲間を心待ちにしております。
それでは!
おまけ
実際のコード (ヘッダのみ)はこんな感じです。
何をやっているかはご想像に……なんてことはなくリクエストがあれば説明します!
#pragma once
#include <map>
#include <optional>
#include <set>
#include <string>
#include <vector>
enum class MonraceId : short;
class MonraceDefinition;
extern std::map<MonraceId, MonraceDefinition> monraces_info;
class MonraceList {
public:
MonraceList(MonraceList &&) = delete;
MonraceList(const MonraceList &) = delete;
MonraceList &operator=(const MonraceList &) = delete;
MonraceList &operator=(MonraceList &&) = delete;
static bool is_valid(MonraceId monrace_id);
static const std::map<MonraceId, std::set<MonraceId>> &get_unified_uniques();
static MonraceList &get_instance();
static MonraceId empty_id();
static bool is_tsuchinoko(MonraceId monrace_id);
std::map<MonraceId, MonraceDefinition>::iterator begin();
std::map<MonraceId, MonraceDefinition>::const_iterator begin() const;
std::map<MonraceId, MonraceDefinition>::iterator end();
std::map<MonraceId, MonraceDefinition>::const_iterator end() const;
std::map<MonraceId, MonraceDefinition>::reverse_iterator rbegin();
std::map<MonraceId, MonraceDefinition>::const_reverse_iterator rbegin() const;
std::map<MonraceId, MonraceDefinition>::reverse_iterator rend();
std::map<MonraceId, MonraceDefinition>::const_reverse_iterator rend() const;
size_t size() const;
MonraceDefinition &emplace(MonraceId monrace_id);
std::map<MonraceId, MonraceDefinition> &get_raw_map();
MonraceDefinition &get_monrace(MonraceId monrace_id);
const MonraceDefinition &get_monrace(MonraceId monrace_id) const;
const std::vector<MonraceId> &get_valid_monrace_ids() const;
const std::vector<std::pair<MonraceId, const MonraceDefinition *>> &get_sorted_monraces() const;
bool can_unify_separate(const MonraceId r_idx) const;
void kill_unified_unique(const MonraceId r_idx);
bool is_selectable(const MonraceId r_idx) const;
void defeat_separated_uniques();
bool is_unified(const MonraceId r_idx) const;
bool exists_separates(const MonraceId r_idx) const;
bool is_separated(const MonraceId r_idx) const;
bool can_select_separate(const MonraceId morace_id, const int hp, const int maxhp) const;
bool order(MonraceId id1, MonraceId id2, bool is_detailed = false) const;
bool order_level(MonraceId id1, MonraceId id2) const;
bool order_level_unique(MonraceId id1, MonraceId id2) const;
MonraceId pick_id_at_random() const;
const MonraceDefinition &pick_monrace_at_random() const;
int calc_defeat_count() const;
void reset_current_numbers();
void reset_all_visuals();
std::optional<std::string> probe_lore(MonraceId monrace_id);
void kill_unique_monster(MonraceId monrace_id);
private:
MonraceList() = default;
static MonraceList instance;
const static std::map<MonraceId, std::set<MonraceId>> unified_uniques;
};