diff --git a/GlyphsJuke.vcxproj b/GlyphsJuke.vcxproj index 0a20daa..62b1492 100644 --- a/GlyphsJuke.vcxproj +++ b/GlyphsJuke.vcxproj @@ -148,6 +148,7 @@ + @@ -155,6 +156,8 @@ + + @@ -166,6 +169,7 @@ + @@ -181,6 +185,7 @@ + diff --git a/GlyphsJuke.vcxproj.filters b/GlyphsJuke.vcxproj.filters index 53a31df..faa7d5c 100644 --- a/GlyphsJuke.vcxproj.filters +++ b/GlyphsJuke.vcxproj.filters @@ -39,6 +39,9 @@ Source Files + + Source Files + @@ -155,6 +158,18 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + diff --git a/src/ElementStore.h b/src/ElementStore.h index f4565ee..5dce7aa 100644 --- a/src/ElementStore.h +++ b/src/ElementStore.h @@ -53,6 +53,7 @@ class ElementStore { for (auto& eptr : performing_) { iElement* e = eptr.get(); if (e->period.end <= now) { + e->Finalize(); eptr = nullptr; } else { e->Update(frame, e->period.Normalize(now)); diff --git a/src/Frame.h b/src/Frame.h index 4932477..3709b79 100644 --- a/src/Frame.h +++ b/src/Frame.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "iDrawable.h" @@ -46,6 +47,8 @@ class Frame : public iDrawable, public iWritable { } } + std::string input; + private: std::vector draw_; std::vector write_; diff --git a/src/Game.h b/src/Game.h index afe836e..b2c26c8 100644 --- a/src/Game.h +++ b/src/Game.h @@ -36,10 +36,12 @@ class Game : public iDrawable, public iWritable { Game(Param&& p); - void Update() { + void Update(const std::string& input) { clock_.Tick(); frame_.Clear(); + frame_.input = input; + UniqPtr next = scene_->Update(frame_); if (next) { scene_ = std::move(next); diff --git a/src/GlyphElementFactory.h b/src/GlyphElementFactory.h index ccfebe9..9ddf32e 100644 --- a/src/GlyphElementFactory.h +++ b/src/GlyphElementFactory.h @@ -30,7 +30,7 @@ class GlyphElementFactory : public iElementFactory { const intmax_t size = std::get(param.custom[2]); auto& font = FindOrCreateFont(name); - auto tex = std::move(font.RenderGlyphs(ConvertUtf8ToUtf16(text), size)); /* TODO */ + auto tex = std::move(font.RenderGlyphs(ConvertStrToWstr(text), size)); /* TODO */ return alloc_->MakeUniq( param.period, std::move(tex), std::move(param.driver)); diff --git a/src/HiraganaMatcher.cc b/src/HiraganaMatcher.cc new file mode 100644 index 0000000..d3fd70f --- /dev/null +++ b/src/HiraganaMatcher.cc @@ -0,0 +1,99 @@ +#include "HiraganaMatcher.h" + +#include +#include + +#include "common.h" + + +static const std::vector> kPatterns = { + { L"ka", L"か", }, + { L"ki", L"き", }, + { L"ku", L"く", }, + { L"ke", L"け", }, + { L"ko", L"こ", }, + { L"sa", L"さ", }, + { L"si", L"し", }, + { L"su", L"す", }, + { L"se", L"せ", }, + { L"so", L"そ", }, + { L"ta", L"た", }, + { L"ti", L"ち", }, + { L"tu", L"つ", }, + { L"te", L"て", }, + { L"to", L"と", }, + { L"na", L"な", }, + { L"ni", L"に", }, + { L"nu", L"ぬ", }, + { L"ne", L"ね", }, + { L"no", L"の", }, + { L"a", L"あ", }, + { L"i", L"い", }, + { L"u", L"う", }, + { L"e", L"え", }, + { L"o", L"お", }, +}; + + +bool gj::HiraganaMatcher::Input(wchar_t c) { + assert(!done()); + + const std::wstring remain = state_.text.substr(state_.match); + const std::wstring newbuf = buffer_ + c; + + const size_t n = newbuf.size(); + + size_t last_match = 0; + bool accept = false; + for (size_t i = 0; i < kPatterns.size(); ++i) { + const auto& pattern = kPatterns[i]; + if (pattern.first.substr(0, n) == newbuf) { + const size_t pn = pattern.second.size(); + if (remain.substr(0, pn) == pattern.second) { + accept = true; + last_match = i; + } + if (pattern.first.size() == n) { + if (!accept) return false; + state_.match += pn; + buffer_ = L""; + UpdateExpects(i); + return true; + } + } + } + + if (accept) { + buffer_ = newbuf; + UpdateExpects(last_match); + } + return accept; +} + +void gj::HiraganaMatcher::UpdateExpects(size_t last_match) { + std::wstring text = state_.text.substr(state_.match); + + expects_ = L""; + if (text.empty()) return; + + if (last_match < SIZE_MAX && buffer_.size()) { + expects_ = kPatterns[last_match].first.substr(buffer_.size()); + text = text.substr(kPatterns[last_match].second.size()); + } + + while (text.size()) { + bool match = false; + for (const auto& pattern : kPatterns) { + const size_t n = pattern.second.size(); + if (text.size() < n) continue; + + if (text.substr(0, n) == pattern.second) { + expects_ += pattern.first; + text = text.substr(n); + match = true; + break; + } + } + if (!match) Abort("hiragana janai"); + } +} \ No newline at end of file diff --git a/src/HiraganaMatcher.h b/src/HiraganaMatcher.h new file mode 100644 index 0000000..e4a9de6 --- /dev/null +++ b/src/HiraganaMatcher.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include "iInputMatcher.h" + +namespace gj { + + +class HiraganaMatcher : public iInputMatcher { + public: + HiraganaMatcher(HiraganaMatcher&&) = default; + HiraganaMatcher(const HiraganaMatcher&) = default; + + HiraganaMatcher& operator=(HiraganaMatcher&&) = default; + HiraganaMatcher& operator=(const HiraganaMatcher&) = default; + + HiraganaMatcher(const std::wstring& text) : state_{text, 0} { + UpdateExpects(); + } + + bool Input(wchar_t c) override; + + const std::wstring& expects() const override { + return expects_; + } + bool done() const override { + return state_.text.size() <= state_.match; + } + const State& state() const override { + return state_; + } + + private: + std::wstring buffer_; + std::wstring expects_; + + State state_; + + void UpdateExpects(size_t last_match = SIZE_MAX); +}; + + +} \ No newline at end of file diff --git a/src/InputWindowElement.h b/src/InputWindowElement.h new file mode 100644 index 0000000..55211fb --- /dev/null +++ b/src/InputWindowElement.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +#include "common.h" +#include "iElement.h" +#include "iElementDriver.h" +#include "iInputMatcher.h" +#include "Text.h" + +namespace gj { + + +class InputWindowElement : public iElement { + public: + struct Param { + UniqPtr matcher; + UniqPtr driver; + + Period period; + + std::wstring text; + }; + + InputWindowElement() = delete; + InputWindowElement(InputWindowElement&&) = delete; + InputWindowElement(const InputWindowElement&) = delete; + + InputWindowElement& operator=(InputWindowElement&&) = delete; + InputWindowElement& operator=(const InputWindowElement&) = delete; + + InputWindowElement(Param&& p) : + iElement(p.period), + matcher_(std::move(p.matcher)), drv_(std::move(p.driver)), + text_(p.text), guide_(matcher_->expects()) { + param_["posX"] = 0; + param_["posY"] = 0; + } + + void Update(Frame& frame, double t) override { + for (auto c : frame.input) { + if (matcher_->done()) break; + if (matcher_->Input(c)) { + guide_ = Text(matcher_->expects()); + } + } + drv_->Update(param_, t); + + const auto posX = std::get(param_["posX"]); + const auto posY = std::get(param_["posY"]); + + text_.SetPosition(posX, posY); + guide_.SetPosition(posX, posY+1); + frame.Add(&text_); + frame.Add(&guide_); + } + + void Finalize() override { + /* TODO score calculation */ + } + + private: + UniqPtr matcher_; + UniqPtr drv_; + + Text text_; + Text guide_; + + iElementDriver::Param param_; +}; + + +} \ No newline at end of file diff --git a/src/InputWindowElementFactory.h b/src/InputWindowElementFactory.h new file mode 100644 index 0000000..7f84aa5 --- /dev/null +++ b/src/InputWindowElementFactory.h @@ -0,0 +1,45 @@ +#pragma once + +#include "common.h" +#include "HiraganaMatcher.h" +#include "iAllocator.h" +#include "iClock.h" +#include "iElementFactory.h" +#include "InputWindowElement.h" + + +namespace gj { + + +class InputWindowElementFactory : public iElementFactory { + public: + InputWindowElementFactory(InputWindowElementFactory&&) = delete; + InputWindowElementFactory(const InputWindowElementFactory&) = delete; + + InputWindowElementFactory& operator=(InputWindowElementFactory&&) = delete; + InputWindowElementFactory& operator=(const InputWindowElementFactory&) = delete; + + InputWindowElementFactory(iAllocator* alloc) : alloc_(alloc) { + } + + UniqPtr Create(Param&& param) override { + if (param.custom.size() != 2) return nullptr; + + const std::string text = std::get(param.custom[0]); + const std::string kana = std::get(param.custom[1]); + + InputWindowElement::Param p; + p.period = param.period; + p.driver = std::move(param.driver); + p.matcher = alloc_->MakeUniq(ConvertStrToWstr(kana)); + p.text = ConvertStrToWstr(text); + + return alloc_->MakeUniq(std::move(p)); + } + + private: + iAllocator* alloc_; +}; + + +} \ No newline at end of file diff --git a/src/PlayScene.cc b/src/PlayScene.cc index 5a5d8b9..e75c898 100644 --- a/src/PlayScene.cc +++ b/src/PlayScene.cc @@ -1,15 +1,18 @@ #include "PlayScene.h" #include "GlyphElementFactory.h" +#include "InputWindowElementFactory.h" gj::PlayScene::PlayScene(Param&& p) : alloc_(p.alloc), logger_(p.logger), w_(p.w), h_(p.h), clock_(p.clock), store_(&clock_, 256) { - GlyphElementFactory glyph(alloc_); + GlyphElementFactory glyph(alloc_); + InputWindowElementFactory inputWin(alloc_); Lua::FactoryMap map; - map["Glyph"] = &glyph; + map["Glyph"] = &glyph; + map["InputWin"] = &inputWin; lua_ = alloc_->MakeUniq( alloc_, &store_, map, "res/score/" + p.score + ".lua"); diff --git a/src/TextureElement.h b/src/TextureElement.h index 5256996..58e2362 100644 --- a/src/TextureElement.h +++ b/src/TextureElement.h @@ -44,6 +44,9 @@ public: frame.Add(&tex_); } + void Finalize() override { + } + private: Texture tex_; UniqPtr drv_; diff --git a/src/Win32Console.cc b/src/Win32Console.cc index f1c39ae..9cb5324 100644 --- a/src/Win32Console.cc +++ b/src/Win32Console.cc @@ -47,6 +47,35 @@ void gj::Win32Console::main() { constexpr CONSOLE_CURSOR_INFO cursor{ 1, FALSE }; SetConsoleCursorInfo(screen_, &cursor); + /* read input */ + { /* critical section */ + std::lock_guard _(mtx_); + + INPUT_RECORD input[256]; + DWORD input_n; + PeekConsoleInput(buffer_, input, 256, &input_n); + if (input_n) { + /* Peek* can retrieve the input but doesn't remove it from the buffer + * so calls Read* to clear. */ + ReadConsoleInput(buffer_, input, 256, &input_n); + } + for (DWORD i = 0; i < input_n; ++i) { + const auto& rec = input[i]; + switch (rec.EventType) { + case KEY_EVENT: + if (rec.Event.KeyEvent.bKeyDown) { + input_ += rec.Event.KeyEvent.uChar.AsciiChar; + } + break; + default: + break; + } + } + } + + + + /* write output */ CHAR_INFO* c = chars_.get(); { /* critical section */ std::lock_guard _(mtx_); diff --git a/src/Win32Console.h b/src/Win32Console.h index 704aa9f..68f0388 100644 --- a/src/Win32Console.h +++ b/src/Win32Console.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -10,6 +11,7 @@ #include #undef NOMINMAX +#include "common.h" #include "iConsole.h" @@ -32,10 +34,13 @@ class Win32Console : public iConsole { tb_main_(alloc, w, h), tb_sub_(alloc, w, h), chars_(std::make_unique(static_cast(w)*h)), win_(GetConsoleWindow()) { - _ASSERT(win_); + if (!win_) gj::Abort("GetConsoleWindow returned nullptr"); screen_ = GetStdHandle(STD_OUTPUT_HANDLE); - _ASSERT(screen_ != INVALID_HANDLE_VALUE); + buffer_ = GetStdHandle(STD_INPUT_HANDLE); + if (screen_ == INVALID_HANDLE_VALUE || buffer_ == INVALID_HANDLE_VALUE) { + gj::Abort("GetStdHandle returned nullptr"); + } CONSOLE_SCREEN_BUFFER_INFOEX size; size.cbSize = sizeof(size); @@ -83,6 +88,13 @@ class Win32Console : public iConsole { std::swap(tb_main_, tb_sub_); } + std::string TakeInput() override { + std::lock_guard _(mtx_); + std::string ret = std::move(input_); + input_ = ""; + return ret; + } + uint32_t width() const override { return w_; } @@ -107,8 +119,11 @@ class Win32Console : public iConsole { std::unique_ptr chars_; HANDLE screen_ = INVALID_HANDLE_VALUE; + HANDLE buffer_ = INVALID_HANDLE_VALUE; HWND win_ = nullptr; + std::string input_; + void main(); }; diff --git a/src/common.h b/src/common.h index 0c75da2..c09a6db 100644 --- a/src/common.h +++ b/src/common.h @@ -18,7 +18,7 @@ using mat3 = ::linalg::mat; using vec3 = ::linalg::vec; -static inline std::wstring ConvertUtf8ToUtf16(const std::string& str) { +static inline std::wstring ConvertStrToWstr(const std::string& str) { std::wstring ret; const void* c = str.c_str(); @@ -33,7 +33,7 @@ static inline std::wstring ConvertUtf8ToUtf16(const std::string& str) { [[noreturn]] static inline void Abort(const std::string& msg) { - MessageBox(NULL, ConvertUtf8ToUtf16(msg).c_str(), L"PROGRAM ABORTED", MB_OK); + MessageBox(NULL, ConvertStrToWstr(msg).c_str(), L"PROGRAM ABORTED", MB_OK); std::exit(1); } diff --git a/src/iConsole.h b/src/iConsole.h index 35d7393..8a7445f 100644 --- a/src/iConsole.h +++ b/src/iConsole.h @@ -26,6 +26,8 @@ class iConsole { virtual Textbuffer& TakeTextbuffer() = 0; virtual void SwapTextbuffer() = 0; + virtual std::string TakeInput() = 0; + virtual uint32_t width() const = 0; virtual uint32_t height() const = 0; }; diff --git a/src/iElement.h b/src/iElement.h index fe55cb7..9cd6669 100644 --- a/src/iElement.h +++ b/src/iElement.h @@ -23,6 +23,8 @@ class iElement { virtual void Update(Frame& frame, double t) = 0; + virtual void Finalize() = 0; + /* Interfaces had better not have a variable but this is for optimization. */ const Period period; }; diff --git a/src/iInputMatcher.h b/src/iInputMatcher.h new file mode 100644 index 0000000..fc61f54 --- /dev/null +++ b/src/iInputMatcher.h @@ -0,0 +1,35 @@ +#pragma once + +#include + + +namespace gj { + + +class iInputMatcher { + public: + struct State { + std::wstring text; + size_t match; + }; + + iInputMatcher(iInputMatcher&&) = default; + iInputMatcher(const iInputMatcher&) = default; + + iInputMatcher& operator=(iInputMatcher&&) = default; + iInputMatcher& operator=(const iInputMatcher&) = default; + + iInputMatcher() = default; + virtual ~iInputMatcher() = default; + + virtual bool Input(wchar_t c) = 0; + + virtual const std::wstring& expects() const = 0; + + virtual bool done() const = 0; + + virtual const State& state() const = 0; +}; + + +} \ No newline at end of file diff --git a/src/main.cc b/src/main.cc index bdf290e..229fb5f 100644 --- a/src/main.cc +++ b/src/main.cc @@ -32,7 +32,7 @@ int main() { param.h = kHeight; gj::Game game(std::move(param)); while (true) { - game.Update(); + game.Update(console.TakeInput()); { auto& fb = console.TakeColorbuffer(); fb.Clear();