-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 130b102
Showing
9 changed files
with
587 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
name: Swift | ||
|
||
on: [push] | ||
|
||
jobs: | ||
build: | ||
|
||
runs-on: macos-latest | ||
|
||
steps: | ||
- uses: swift-actions/setup-swift@v1 | ||
with: | ||
swift-version: "5.7.0" | ||
- name: Get swift version | ||
run: swift --version | ||
- uses: actions/checkout@v3 | ||
- name: Build | ||
run: swift build | xcpretty | ||
- name: Run tests | ||
run: swift test | xcpretty |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
.DS_Store | ||
/.build | ||
/Packages | ||
/*.xcodeproj | ||
xcuserdata/ | ||
DerivedData/ | ||
.swiftpm/config/registries.json | ||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata | ||
.netrc | ||
.idea/ | ||
*.sn |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// swift-tools-version: 5.7 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "EffectiveNovelCore", | ||
platforms: [ | ||
.macOS(.v10_15), | ||
.iOS(.v16) | ||
], | ||
products: [ | ||
// Products define the executables and libraries a package produces, and make them visible to other packages. | ||
.library( | ||
name: "EffectiveNovelCore", | ||
targets: ["EffectiveNovelCore"]), | ||
], | ||
dependencies: [ | ||
// Dependencies declare other packages that this package depends on. | ||
// .package(url: /* package url */, from: "1.0.0"), | ||
], | ||
targets: [ | ||
// Targets are the basic building blocks of a package. A target can define a module or a test suite. | ||
// Targets can depend on other targets in this package, and on products in packages this package depends on. | ||
.target( | ||
name: "EffectiveNovelCore", | ||
dependencies: []), | ||
.testTarget( | ||
name: "EffectiveNovelCoreTests", | ||
dependencies: ["EffectiveNovelCore"]), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
# Effective Novel Core | ||
|
||
This is novel text parse and stream provide package. | ||
|
||
## About | ||
This is novel engine. | ||
|
||
This effectively helps to display the characters of the novel. | ||
|
||
This lib doesn't include UI layer. | ||
This only provides parse and output stream display event. (I'm going to will link CLI and iOS Example.) | ||
|
||
Also, this lib is not optimized novel game. | ||
Because this doesn't have if functioned, macro, subroutine. | ||
|
||
## Syntax | ||
| command | DisplayEvent | mean | | ||
|------------------|-------------------------|---------------------------------------------------------------------| | ||
| n | `.newline` | newline | | ||
| tw | `.tapWait` | tap wait | | ||
| twn | `tapWaitAndNewline` | tap wait and newline | | ||
| cl | `.clear` | clear | | ||
| delay speed=xxxx | `.delay(speed: Double)` | change delay character displayed speed. speed unit is milliseconds. | | ||
| resetdelay | `.resetDelay` | reset delay speed | | ||
| e | `.end` | stop script novel end point | | ||
|
||
``` | ||
start text[n] | ||
tap waiting and newline[twn] | ||
[cl] cleared text. | ||
very fast stream after this text[delay speed=2][n] | ||
[resetdelay]reset delay speed.[n] | ||
end. [e] | ||
``` | ||
|
||
## Usage | ||
|
||
```swift | ||
|
||
// 1. get `NovelController` instance | ||
let controller = NovelController() | ||
|
||
// 2. pass to raw novel text | ||
controller.load(rawText: rawText) | ||
|
||
// 3. start() and listening stream | ||
controller.start() | ||
.sink { event in | ||
switch event { | ||
case .character(let char): | ||
displayCharacter(char) | ||
// and any command handling | ||
} | ||
} | ||
.store(in: &cancellables) | ||
|
||
// (4.) show text until wait tag | ||
controller.showTextUntilWaitTag() | ||
|
||
// (5.) resume tap wait | ||
// If you want to start from any index number, you can use `controller.resume(at: 100)` | ||
controller.resume() | ||
|
||
// (6.) interrupt | ||
controller.interrupt() | ||
|
||
|
||
``` | ||
|
||
|
||
|
||
## Examples | ||
- [ ] CLI novel reader | ||
- [ ] iOS novel reader | ||
|
||
## Todo | ||
- value input |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
// | ||
// Created by osushi on 2022/09/23. | ||
// | ||
|
||
import Foundation | ||
import Combine | ||
|
||
enum NovelState { | ||
case loadWait, prepare, running, pause | ||
} | ||
|
||
let defaultSpeed: Double = 250 | ||
|
||
public class NovelController { | ||
|
||
public init() { | ||
} | ||
|
||
private var internalOutputStream = PassthroughSubject<DisplayEvent, Never>() | ||
|
||
private var displayEvents: [DisplayEvent] = [] | ||
|
||
private var cancellable: Set<AnyCancellable> = [] | ||
|
||
private(set) var index: Int = 0 | ||
|
||
private(set) var speed: Double = defaultSpeed | ||
|
||
private(set) var state = NovelState.loadWait | ||
|
||
public func load(raw: String) { | ||
let parser = ScriptParser() | ||
state = .prepare | ||
index = 0 | ||
|
||
displayEvents = parser.parse(rawAllString: raw) | ||
} | ||
|
||
public func start() -> AnyPublisher<DisplayEvent, Never> { | ||
switch state { | ||
case .prepare: | ||
state = .running | ||
startLoop() | ||
default: | ||
print("now state is not prepare. now state: \(state)") | ||
} | ||
|
||
return internalOutputStream.eraseToAnyPublisher() | ||
} | ||
|
||
public func interrupt() { | ||
switch state { | ||
case .running, .pause: | ||
reset() | ||
default: | ||
print("now state is not running or pause. now state: \(state)") | ||
} | ||
} | ||
|
||
public func resume() { | ||
switch state { | ||
case .pause: | ||
state = .running | ||
default: | ||
print("now state is not pause. now state: \(state)") | ||
} | ||
} | ||
|
||
public func resume(at resumeIndex: Int) { | ||
switch state { | ||
case .pause: | ||
index = resumeIndex | ||
state = .running | ||
default: | ||
print("now state is not pause. now state: \(state)") | ||
} | ||
} | ||
|
||
public func reset() { | ||
state = .loadWait | ||
displayEvents = [] | ||
index = 0 | ||
} | ||
|
||
public func showTextUntilWaitTag() { | ||
let offset: Int = index | ||
|
||
let checkListRange = displayEvents[offset..<displayEvents.count] | ||
let endIndex = checkListRange | ||
.firstIndex(where: { $0 == .tapWaitAndNewline || $0 == .tapWait || $0 == .newline }) | ||
.map { $0 + index } ?? index | ||
|
||
let events = displayEvents[(index + 1)...(endIndex - 1)] | ||
|
||
index += events.count | ||
events.forEach { internalOutputStream.send($0) } | ||
} | ||
|
||
private func startLoop() { | ||
Task { | ||
// NOTE: wait for preparing subscribe stream client. | ||
try! await Task.sleep(nanoseconds: 100000) | ||
|
||
while (true) { | ||
switch state { | ||
case .running: | ||
if displayEvents.isEmpty { | ||
reset() | ||
break | ||
} | ||
|
||
let event = displayEvents[index] | ||
|
||
internalOutputStream.send(event) | ||
|
||
// handle on interrupted | ||
if state == .loadWait { | ||
reset() | ||
break | ||
} | ||
|
||
// finish | ||
if displayEvents[index] == .end { | ||
state = .prepare | ||
break | ||
} | ||
|
||
index += 1 | ||
await handleEvent(event: event) | ||
case .prepare: | ||
break | ||
case .loadWait: | ||
break | ||
default: | ||
// clock | ||
try! await Task.sleep(nanoseconds: 10) | ||
continue | ||
} | ||
|
||
// interrupt | ||
if state == .prepare { break } | ||
|
||
// clock | ||
try! await Task.sleep(nanoseconds: 10) | ||
} | ||
} | ||
} | ||
|
||
private func handleEvent(event: DisplayEvent) async { | ||
switch event { | ||
case .character(_): | ||
try! await Task.sleep(nanoseconds: UInt64(speed * Double(pow(10.0, 6)))) | ||
case .delay(let duration): | ||
speed = duration | ||
case .resetDelay: | ||
speed = defaultSpeed | ||
case .tapWait, .tapWaitAndNewline: | ||
state = .pause | ||
default: | ||
print("default") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// | ||
// Created by osushi on 2022/09/23. | ||
// | ||
|
||
import Foundation | ||
|
||
protocol Parser { | ||
func parse(rawAllString: String) -> [DisplayEvent] | ||
} | ||
|
||
struct ScriptParser: Parser { | ||
func parse(rawAllString raw: String) -> [DisplayEvent] { | ||
var rawAllString = raw | ||
rawAllString.removeAll(where: { $0 == "\n" }) | ||
|
||
return rawAllString.components(separatedBy: "[") | ||
.filter { !$0.isEmpty } | ||
.map { (raw: $0, isCommandInclude: $0.contains("]")) } | ||
.map { $0.isCommandInclude ? splitCommandIncludingText(raw: $0.raw) : stringToCharacter(string: $0.raw) } | ||
.flatMap { $0 } | ||
} | ||
|
||
// cm] or cm]text | ||
private func splitCommandIncludingText(raw: String) -> [DisplayEvent] { | ||
var result: [DisplayEvent] = [] | ||
let commandAndText = raw.components(separatedBy: "]") | ||
|
||
result.append(DisplayEvent.parseCommand(rawCommand: commandAndText.first!)) | ||
|
||
if let text = commandAndText.last, !text.isEmpty { | ||
result += stringToCharacter(string: text) | ||
} | ||
|
||
return result | ||
} | ||
|
||
private func stringToCharacter(string: String) -> [DisplayEvent] { | ||
string.map { c in DisplayEvent.character(char: c) } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// | ||
// Created by osushi on 2022/09/23. | ||
// | ||
|
||
public enum DisplayEvent: Equatable { | ||
case character(char: Character) | ||
case newline | ||
case tapWait | ||
case tapWaitAndNewline | ||
case clear | ||
|
||
case resetDelay | ||
case delay(speed: Double) | ||
|
||
case end | ||
|
||
static func parseCommand(rawCommand: String) -> DisplayEvent { | ||
switch rawCommand { | ||
case "n": | ||
return .newline | ||
case "tw": | ||
return .tapWait | ||
case "twn": | ||
return .tapWaitAndNewline | ||
case "cl": | ||
return .clear | ||
case "resetdelay": | ||
return .resetDelay | ||
case (let command) where command.contains("delay"): | ||
let speed = Double(command.split(separator: "=").last!)! | ||
return .delay(speed: speed) | ||
case "e": | ||
return .end | ||
default: | ||
fatalError(file: #file) | ||
} | ||
} | ||
} |
Oops, something went wrong.