diff options
-rw-r--r-- | Cargo.lock | 106 | ||||
-rw-r--r-- | Cargo.toml | 7 | ||||
-rw-r--r-- | src/display/draw_box.rs | 17 | ||||
-rw-r--r-- | src/display/viewport.rs | 107 | ||||
-rw-r--r-- | src/game.rs | 55 | ||||
-rw-r--r-- | src/main.rs | 8 | ||||
-rw-r--r-- | src/messages.rs | 186 | ||||
-rw-r--r-- | src/messages.toml | 2 | ||||
-rw-r--r-- | src/settings.rs | 1 | ||||
-rw-r--r-- | src/types/mod.rs | 6 |
10 files changed, 443 insertions, 52 deletions
diff --git a/Cargo.lock b/Cargo.lock index 5eacba21255f..562bc2ee336b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,6 +132,15 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "c2-chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "cc" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -297,6 +306,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "getrandom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "humantime" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -326,6 +344,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "lazy_static" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "libc" @@ -385,6 +406,11 @@ dependencies = [ ] [[package]] +name = "maplit" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "memchr" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -473,6 +499,11 @@ dependencies = [ ] [[package]] +name = "ppv-lite86" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "prettytable-rs" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -554,6 +585,19 @@ dependencies = [ ] [[package]] +name = "rand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_pcg 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "rand_chacha" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -563,6 +607,16 @@ dependencies = [ ] [[package]] +name = "rand_chacha" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "rand_core" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -576,6 +630,14 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "rand_core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "rand_hc" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -584,6 +646,14 @@ dependencies = [ ] [[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "rand_isaac" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -624,6 +694,15 @@ dependencies = [ ] [[package]] +name = "rand_pcg" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "rand_xorshift" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -806,6 +885,11 @@ dependencies = [ ] [[package]] +name = "spin" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -910,6 +994,14 @@ dependencies = [ ] [[package]] +name = "toml" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "traitobject" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -997,12 +1089,15 @@ dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "log4rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", + "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "proptest 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "proptest-derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1037,6 +1132,7 @@ dependencies = [ "checksum bstr 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc0572e02f76cb335f309b19e0a0d585b4f62788f7d26de2a13a836a637385f" "checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" +"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" "checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" @@ -1057,6 +1153,7 @@ dependencies = [ "checksum flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f87e68aa82b2de08a6e037f1385455759df6e445a8df5e005b4297191dbf18aa" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +"checksum getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e65cce4e5084b14874c4e7097f38cab54f47ee554f9194673456ea379dcc4c55" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" @@ -1068,6 +1165,7 @@ dependencies = [ "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum log-mdc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" "checksum log4rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "100052474df98158c0738a7d3f4249c99978490178b5f9f68cd835ac57adbd1b" +"checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum miniz-sys 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9e3ae51cea1576ceba0dde3d484d30e6e5b86dee0b2d412fe3a16a15c98202" "checksum miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c468f2369f07d651a5d0bb2c9079f8488a66d5466efe42d0c5c6466edcb7f71e" @@ -1079,6 +1177,7 @@ dependencies = [ "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" "checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" +"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" "checksum prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd04b170004fa2daccf418a7f8253aaf033c27760b5f225889024cf66d7ac2e" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum proptest 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2afed8cbdc8a64b58a5c021757a782351ec1afee85be374872721c84d5da5d80" @@ -1086,14 +1185,19 @@ dependencies = [ "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +"checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +"checksum rand_chacha 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e193067942ef6f485a349a113329140d0ab9e2168ce92274499bb0e9a4190d9d" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" "checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" +"checksum rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "615e683324e75af5d43d8f7a39ffe3ee4a9dc42c5c701167a71dc59c3a493aca" "checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" "checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" "checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +"checksum rand_pcg 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e196346cbbc5c70c77e7b4926147ee8e383a38ee4d15d58a08098b169e492b6" "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" @@ -1117,6 +1221,7 @@ dependencies = [ "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum serde_test 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" "checksum serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "38b08a9a90e5260fe01c6480ec7c811606df6d3a660415808c3c3fa8ed95b582" +"checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)" = "641e117d55514d6d918490e47102f7e08d096fdde360247e4a10f7a91a8478d3" "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" @@ -1128,6 +1233,7 @@ dependencies = [ "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" +"checksum toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8c96d7873fa7ef8bdeb3a9cda3ac48389b4154f32b9803b4bc26220b677b039" "checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" "checksum typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" diff --git a/Cargo.toml b/Cargo.toml index c08edfe8fd4f..a9079b5dde63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,17 +5,20 @@ authors = ["Griffin Smith <root@gws.fyi>"] edition = "2018" [dependencies] +clap = {version = "^2.33.0", features = ["yaml"]} config = "*" itertools = "*" lazy_static = "*" log = "*" log4rs = "*" +maplit = "^1.0.1" +prettytable-rs = "^0.8" proptest = "0.9.3" proptest-derive = "*" +rand = {version = "^0.7.0", features = ["small_rng"]} serde = "^1.0.8" serde_derive = "^1.0.8" termion = "*" -clap = {version = "^2.33.0", features = ["yaml"]} -prettytable-rs = "^0.8" +toml = "^0.5.1" [dev-dependencies] diff --git a/src/display/draw_box.rs b/src/display/draw_box.rs index 986f09a49f7c..5dc1627a298d 100644 --- a/src/display/draw_box.rs +++ b/src/display/draw_box.rs @@ -1,10 +1,12 @@ use crate::display::utils::clone_times; use crate::display::utils::times; +use crate::types::BoundingBox; use crate::types::Dimensions; use itertools::Itertools; use proptest::prelude::Arbitrary; use proptest::strategy; use proptest_derive::Arbitrary; +use std::io::{self, Write}; // Box Drawing // 0 1 2 3 4 5 6 7 8 9 A B C D E F @@ -164,6 +166,21 @@ pub fn make_box(style: BoxStyle, dims: Dimensions) -> String { } } +/// Draw the box described by the given BoundingBox's position and dimensions to +/// the given output, with the given style +pub fn draw_box<W: Write>( + out: &mut W, + bbox: BoundingBox, + style: BoxStyle, +) -> io::Result<()> { + write!( + out, + "{}{}", + bbox.position.cursor_goto(), + make_box(style, bbox.dimensions) + ) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/display/viewport.rs b/src/display/viewport.rs index bd2fac07146f..24cc5272cb8e 100644 --- a/src/display/viewport.rs +++ b/src/display/viewport.rs @@ -1,5 +1,8 @@ +use super::BoxStyle; use super::Draw; -use super::{make_box, BoxStyle}; +use crate::display::draw_box::draw_box; +use crate::display::utils::clone_times; +use crate::display::utils::times; use crate::types::{BoundingBox, Position, Positioned}; use std::fmt::{self, Debug}; use std::io::{self, Write}; @@ -10,36 +13,47 @@ pub struct Viewport<W> { /// Generally the size of the terminal, and positioned at 0, 0 pub outer: BoundingBox, + /// The box describing the game part of the viewport. + pub game: BoundingBox, + /// The box describing the inner part of the viewport /// - /// Its position is relative to `outer.inner()`, and its size should generally not - /// be smaller than outer + /// Its position is relative to `outer.inner()`, and its size should + /// generally not be smaller than outer pub inner: BoundingBox, /// The actual screen that the viewport writes to pub out: W, } - -impl<W> Debug for Viewport<W> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Viewport {{ outer: {:?}, inner: {:?}, out: <OUT> }}", - self.outer, self.inner - ) +impl<W> Viewport<W> { + pub fn new(outer: BoundingBox, inner: BoundingBox, out: W) -> Self { + Viewport { + outer, + inner, + out, + game: outer.move_tr_corner(Position { x: 0, y: 1 }), + } } -} -impl<W> Viewport<W> { /// Returns true if the (inner-relative) position of the given entity is /// visible within this viewport - fn visible<E: Positioned>(&self, ent: &E) -> bool { - self.on_screen(ent.position()).within(self.outer.inner()) + pub fn visible<E: Positioned>(&self, ent: &E) -> bool { + self.on_screen(ent.position()).within(self.game.inner()) } /// Convert the given inner-relative position to one on the actual screen fn on_screen(&self, pos: Position) -> Position { - pos + self.inner.position + self.outer.inner().position + pos + self.inner.position + self.game.inner().position + } +} + +impl<W> Debug for Viewport<W> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Viewport {{ outer: {:?}, inner: {:?}, out: <OUT> }}", + self.outer, self.inner + ) } } @@ -49,25 +63,46 @@ impl<W: Write> Viewport<W> { if !self.visible(entity) { return Ok(()); } - write!( - self, - "{}", - (entity.position() - + self.inner.position - + self.outer.inner().position) - .cursor_goto() - )?; + self.cursor_goto(entity.position())?; entity.do_draw(self) } - /// Clear whatever is drawn at the given inner-relative position, if visible + /// Move the cursor to the given inner-relative position + pub fn cursor_goto(&mut self, pos: Position) -> io::Result<()> { + write!(self, "{}", self.on_screen(pos).cursor_goto()) + } + + /// Clear whatever single character is drawn at the given inner-relative + /// position, if visible pub fn clear(&mut self, pos: Position) -> io::Result<()> { write!(self, "{} ", self.on_screen(pos).cursor_goto(),) } /// Initialize this viewport by drawing its outer box to the screen pub fn init(&mut self) -> io::Result<()> { - write!(self, "{}", make_box(BoxStyle::Thin, self.outer.dimensions)) + draw_box(self, self.game, BoxStyle::Thin) + } + + /// Write a message to the message area on the screen + /// + /// Will overwrite any message already present, and if the given message is + /// longer than the screen will truncate. This means callers should handle + /// message buffering and ellipsisization + pub fn write_message(&mut self, msg: &str) -> io::Result<()> { + write!( + self, + "{}{}{}", + self.outer.position.cursor_goto(), + if msg.len() <= self.outer.dimensions.w as usize { + msg + } else { + &msg[0..self.outer.dimensions.w as usize] + }, + clone_times::<_, String>( + " ".to_string(), + self.outer.dimensions.w - msg.len() as u16 + ), + ) } } @@ -99,24 +134,24 @@ mod tests { #[test] fn test_visible() { - assert!(Viewport { - outer: BoundingBox::at_origin(Dimensions { w: 10, h: 10 }), - inner: BoundingBox { + assert!(Viewport::new( + BoundingBox::at_origin(Dimensions { w: 10, h: 10 }), + BoundingBox { position: Position { x: -10, y: -10 }, dimensions: Dimensions { w: 15, h: 15 }, }, - out: (), - } + () + ) .visible(&Position { x: 13, y: 13 })); - assert!(!Viewport { - outer: BoundingBox::at_origin(Dimensions { w: 10, h: 10 }), - inner: BoundingBox { + assert!(!Viewport::new( + BoundingBox::at_origin(Dimensions { w: 10, h: 10 }), + BoundingBox { position: Position { x: -10, y: -10 }, dimensions: Dimensions { w: 15, h: 15 }, }, - out: (), - } + (), + ) .visible(&Position { x: 1, y: 1 })); } diff --git a/src/game.rs b/src/game.rs index 6274ef573f58..b619f13423f5 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,17 +1,21 @@ +use crate::display::{self, Viewport}; +use crate::entities::Character; +use crate::messages::message; use crate::settings::Settings; +use crate::types::command::Command; use crate::types::Positioned; use crate::types::{BoundingBox, Dimensions, Position}; +use rand::rngs::SmallRng; +use rand::SeedableRng; use std::io::{self, StdinLock, StdoutLock, Write}; use termion::input::Keys; use termion::input::TermRead; use termion::raw::RawTerminal; -use crate::display::{self, Viewport}; -use crate::entities::Character; -use crate::types::command::Command; - type Stdout<'a> = RawTerminal<StdoutLock<'a>>; +type Rng = SmallRng; + /// The full state of a running Game pub struct Game<'a> { settings: Settings, @@ -23,6 +27,12 @@ pub struct Game<'a> { /// The player character character: Character, + + /// The messages that have been said to the user, in forward time order + messages: Vec<String>, + + /// A global random number generator for the game + rng: Rng, } impl<'a> Game<'a> { @@ -33,18 +43,21 @@ impl<'a> Game<'a> { w: u16, h: u16, ) -> Game<'a> { + let rng = match settings.seed { + Some(seed) => SmallRng::seed_from_u64(seed), + None => SmallRng::from_entropy(), + }; Game { - settings: settings, - viewport: Viewport { - outer: BoundingBox::at_origin(Dimensions { w, h }), - inner: BoundingBox::at_origin(Dimensions { - w: w - 2, - h: h - 2, - }), - out: stdout, - }, + settings, + rng, + viewport: Viewport::new( + BoundingBox::at_origin(Dimensions { w, h }), + BoundingBox::at_origin(Dimensions { w: w - 2, h: h - 2 }), + stdout, + ), keys: stdin.keys(), character: Character::new(), + messages: Vec::new(), } } @@ -53,15 +66,29 @@ impl<'a> Game<'a> { !pos.within(self.viewport.inner) } + /// Draw all the game entities to the screen fn draw_entities(&mut self) -> io::Result<()> { self.viewport.draw(&self.character) } + /// Get a message from the global map based on the rng in this game + fn message(&mut self, name: &str) -> &'static str { + message(name, &mut self.rng) + } + + /// Say a message to the user + fn say(&mut self, message_name: &str) -> io::Result<()> { + let message = self.message(message_name); + self.messages.push(message.to_string()); + self.viewport.write_message(message) + } + /// Run the game pub fn run(mut self) -> io::Result<()> { info!("Running game"); self.viewport.init()?; self.draw_entities()?; + self.say("global.welcome")?; self.flush()?; loop { let mut old_position = None; @@ -86,7 +113,7 @@ impl<'a> Game<'a> { self.viewport.clear(old_pos)?; self.viewport.draw(&self.character)?; } - None => () + None => (), } self.flush()?; debug!("{:?}", self.character); diff --git a/src/main.rs b/src/main.rs index f2c3d00f96d7..6b0ae18181ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,18 +3,26 @@ extern crate termion; extern crate log; extern crate config; extern crate log4rs; +extern crate serde; +extern crate toml; #[macro_use] extern crate serde_derive; #[macro_use] extern crate clap; #[macro_use] extern crate prettytable; +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate maplit; + mod display; mod game; #[macro_use] mod types; mod entities; +mod messages; mod settings; use clap::App; diff --git a/src/messages.rs b/src/messages.rs new file mode 100644 index 000000000000..2b9f098f98ca --- /dev/null +++ b/src/messages.rs @@ -0,0 +1,186 @@ +use rand::seq::SliceRandom; +use rand::Rng; +use serde::de::MapAccess; +use serde::de::SeqAccess; +use serde::de::Visitor; +use std::collections::HashMap; +use std::fmt; +use std::marker::PhantomData; + +#[derive(Deserialize, Debug, PartialEq, Eq)] +#[serde(untagged)] +enum Message<'a> { + Single(&'a str), + Choice(Vec<&'a str>), +} + +impl<'a> Message<'a> { + fn resolve<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<&'a str> { + use Message::*; + match self { + Single(msg) => Some(*msg), + Choice(msgs) => msgs.choose(rng).map(|msg| *msg), + } + } +} + +#[derive(Debug, PartialEq, Eq)] +enum NestedMap<'a> { + Direct(Message<'a>), + Nested(HashMap<&'a str, NestedMap<'a>>), +} + +impl<'a> NestedMap<'a> { + fn lookup(&'a self, path: &str) -> Option<&'a Message<'a>> { + use NestedMap::*; + let leaf = + path.split(".") + .fold(Some(self), |current, key| match current { + Some(Nested(m)) => m.get(key), + _ => None, + }); + match leaf { + Some(Direct(msg)) => Some(msg), + _ => None, + } + } +} + +struct NestedMapVisitor<'a> { + marker: PhantomData<fn() -> NestedMap<'a>>, +} + +impl<'a> NestedMapVisitor<'a> { + fn new() -> Self { + NestedMapVisitor { + marker: PhantomData, + } + } +} + +impl<'de> Visitor<'de> for NestedMapVisitor<'de> { + type Value = NestedMap<'de>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str( + "A message, a list of messages, or a nested map of messages", + ) + } + + fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> { + Ok(NestedMap::Direct(Message::Single(v))) + } + + fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> + where + A: SeqAccess<'de>, + { + let mut choices = Vec::with_capacity(seq.size_hint().unwrap_or(0)); + while let Some(choice) = seq.next_element()? { + choices.push(choice); + } + Ok(NestedMap::Direct(Message::Choice(choices))) + } + + fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> + where + A: MapAccess<'de>, + { + let mut nested = HashMap::with_capacity(map.size_hint().unwrap_or(0)); + while let Some((k, v)) = map.next_entry()? { + nested.insert(k, v); + } + Ok(NestedMap::Nested(nested)) + } +} + +impl<'de> serde::Deserialize<'de> for NestedMap<'de> { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(NestedMapVisitor::new()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_deserialize_nested_map() { + let src = r#" +[global] +hello = "Hello World!" + +[foo.bar] +single = "Single" +choice = ["Say this", "Or this"] +"#; + let result = toml::from_str(src); + assert_eq!( + result, + Ok(NestedMap::Nested(hashmap! { + "global" => NestedMap::Nested(hashmap!{ + "hello" => NestedMap::Direct(Message::Single("Hello World!")), + }), + "foo" => NestedMap::Nested(hashmap!{ + "bar" => NestedMap::Nested(hashmap!{ + "single" => NestedMap::Direct(Message::Single("Single")), + "choice" => NestedMap::Direct(Message::Choice( + vec!["Say this", "Or this"] + )) + }) + }) + })) + ) + } + + #[test] + fn test_lookup() { + let map: NestedMap<'static> = toml::from_str( + r#" +[global] +hello = "Hello World!" + +[foo.bar] +single = "Single" +choice = ["Say this", "Or this"] +"#, + ) + .unwrap(); + + assert_eq!( + map.lookup("global.hello"), + Some(&Message::Single("Hello World!")) + ); + assert_eq!( + map.lookup("foo.bar.single"), + Some(&Message::Single("Single")) + ); + assert_eq!( + map.lookup("foo.bar.choice"), + Some(&Message::Choice(vec!["Say this", "Or this"])) + ); + } +} + +static MESSAGES_RAW: &'static str = include_str!("messages.toml"); + +lazy_static! { + static ref MESSAGES: NestedMap<'static> = + toml::from_str(MESSAGES_RAW).unwrap(); +} + +/// Look up a game message based on the given (dot-separated) name, with the +/// given random generator used to select from choice-based messages +pub fn message<R: Rng + ?Sized>(name: &str, rng: &mut R) -> &'static str { + use Message::*; + MESSAGES + .lookup(name) + .and_then(|msg| msg.resolve(rng)) + .unwrap_or_else(|| { + error!("Message not found: {}", name); + "Message not found" + }) +} diff --git a/src/messages.toml b/src/messages.toml new file mode 100644 index 000000000000..04746462d580 --- /dev/null +++ b/src/messages.toml @@ -0,0 +1,2 @@ +[global] +welcome = "Welcome to Xanthous! It's dangerous out there, why not stay inside?" diff --git a/src/settings.rs b/src/settings.rs index 06f0d4e9d7de..8444bf80eec8 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -48,6 +48,7 @@ impl Logging { #[derive(Debug, Deserialize)] pub struct Settings { + pub seed: Option<u64>, pub logging: Logging, } diff --git a/src/types/mod.rs b/src/types/mod.rs index 146dfac9d99b..ab66a50cc218 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -66,6 +66,12 @@ impl BoundingBox { pub fn inner(self) -> BoundingBox { self + UNIT_POSITION - UNIT_DIMENSIONS - UNIT_DIMENSIONS } + + /// Moves the top right corner of the bounding box by the offset specified + /// by the given position, keeping the lower right corner in place + pub fn move_tr_corner(self, offset: Position) -> BoundingBox { + self + offset - Dimensions { w: offset.x as u16, h: offset.y as u16 } + } } impl ops::Add<Position> for BoundingBox { |