Compare commits
6
Commits
a2196feb78
...
1d14076468
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d14076468 | ||
|
|
5a055c048d | ||
|
|
64a6e1d42d | ||
|
|
f594f12f35 | ||
|
|
303c587aee | ||
|
|
5610c2c343 |
Generated
+94
-29
@@ -18,6 +18,12 @@ version = "0.1.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.8.12"
|
version = "0.8.12"
|
||||||
@@ -228,15 +234,6 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "client"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"bytemuck",
|
|
||||||
"pbio",
|
|
||||||
"shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codespan-reporting"
|
name = "codespan-reporting"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
@@ -328,6 +325,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.21"
|
version = "0.8.21"
|
||||||
@@ -398,12 +404,32 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fdeflate"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
|
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide 0.9.1",
|
||||||
|
"zlib-rs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foldhash"
|
name = "foldhash"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -443,6 +469,15 @@ version = "0.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "game"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"pbio",
|
||||||
|
"png",
|
||||||
|
"sim",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gethostname"
|
name = "gethostname"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -798,6 +833,26 @@ dependencies = [
|
|||||||
"paste",
|
"paste",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "naga"
|
name = "naga"
|
||||||
version = "27.0.3"
|
version = "27.0.3"
|
||||||
@@ -854,10 +909,6 @@ dependencies = [
|
|||||||
"jni-sys 0.3.0",
|
"jni-sys 0.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "netsim"
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -1168,7 +1219,7 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "pbio"
|
name = "pbio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://codeberg.org/irrlicht/rust-pbio.git#2d814718200a5585216f90766693bca1582b3c0e"
|
source = "git+https://git.bnd.wtf/irrlicht/pbio.git?branch=truecolor16#8c647d3700fe3196d0fd511e80bc3c60b0b8f1ae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"pollster",
|
"pollster",
|
||||||
@@ -1220,6 +1271,19 @@ version = "0.2.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide 0.8.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "polling"
|
name = "polling"
|
||||||
version = "3.11.0"
|
version = "3.11.0"
|
||||||
@@ -1471,27 +1535,22 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "server"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"bytemuck",
|
|
||||||
"shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "shared"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"bytemuck",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sim"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd_cesu8"
|
name = "simd_cesu8"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -2421,3 +2480,9 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zlib-rs"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["shared", "client", "server", "netsim"]
|
members = ["sim", "game"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|||||||
@@ -0,0 +1,256 @@
|
|||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,25,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,146,146,25,146,25,25,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,146,146,146,146,146,25,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,25,75,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,25,146,146,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,74,25,74,75,25,25,25,146,25,25,146,146,146,146,146,146,25,146,146,146,25,146,25,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,25,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,74,74,74,75,75,25,25,75,75,74,146,25,146,146,146,146,146,146,146,146,146,25,25,74,75,74,75,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,146,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,146,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,75,75,75,75,25,25,74,74,74,75,74,146,146,146,146,146,146,146,146,146,146,146,74,75,74,75,74,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,25,146,146,74,75,74,74,75,74,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,75,74,25,75,25,146,74,75,75,146,146,146,146,146,146,146,146,146,74,75,74,25,75,74,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,146
|
||||||
|
25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,146,25,25,25,25,25,25,146,146,25,146,25,25,25,25,25,25,25,75,75,75,146,146,146,146,75,75,75,75,75,74,75,74,75,75,146,146,146,146,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,25,74,75,74,25,74,25,25,75,75,74,75,75,75,75,74,146,146,146,146,146,146,146,146,146,75,75,75,74,25,25,75,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146
|
||||||
|
25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,25,146,25,25,25,25,25,25,25,75,75,74,75,74,25,25,146,75,75,75,74,146,75,75,75,74,25,146,146,146,146,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,74,75,25,75,74,25,25,75,74,74,74,74,75,75,74,75,146,146,146,146,146,146,146,75,74,74,75,74,75,74,75,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,146,146,146,25,25,25,25,25,146,146,25,146,146,146,146,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,146,146,146,146,146,146,146,25
|
||||||
|
25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,25,146,25,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,75,74,75,75,25,25,25,74,75,75,74,74,74,75,74,146,146,25,146,146,146,146,146,25,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,25,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,75,75,75,74,75,75,25,25,74,74,25,74,74,25,75,75,74,146,25,146,146,146,146,146,75,74,74,74,74,74,75,74,25,25,25,25,25,25,25,146,25,146,25,146,146,146,146,146,146,146,25,25,25,25,25,146,146,25,146,146,146,25,146,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146
|
||||||
|
25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,25,25,146,25,146,75,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,146,74,25,25,25,25,74,75,25,25,74,74,74,146,146,146,25,146,146,146,25,146,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,74,74,74,75,74,74,74,74,75,75,25,25,25,25,74,74,75,25,146,146,146,146,146,146,146,75,74,74,75,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,146,146,146,25,25,75,74,74,75,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25
|
||||||
|
25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,74,146,25,75,146,25,25,25,75,25,74,74,75,74,75,146,146,25,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,75,75,75,74,74,75,25,25,75,75,74,25,25,25,75,74,75,75,146,25,25,146,146,146,146,146,74,74,74,75,25,25,25,25,25,25,25,25,25,25,146,25,146,25,25,146,146,146,146,146,74,74,74,74,75,75,75,75,74,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,146,146,146,146,146,146,146,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,25,25,25,75,74,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,75,74,25,75,146,146,146,75,75,25,74,75,75,74,75,146,146,146,146,25,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,74,74,75,74,75,25,25,25,25,25,75,74,74,25,74,75,146,146,146,146,146,146,146,74,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,146,74,75,75,75,74,74,75,75,75,75,74,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25
|
||||||
|
25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,25,74,75,74,74,74,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,74,75,75,74,74,74,74,75,25,25,74,146,146,75,74,146,146,146,25,146,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,75,74,74,74,75,75,75,25,25,25,25,75,75,25,25,74,25,146,146,146,146,146,146,146,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,25,25,146,75,74,75,75,75,25,146,146,146,146,146,146,25,25,25,25,25,25,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,25,146,146,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,25,146,146,146,74,74,75,75,146,146,25,74,74,74,75,74,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,74,74,74,146,74,74,74,75,75,146,74,75,25,146,75,74,146,146,146,146,25,146,146,146,146,146,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,74,74,74,75,75,74,25,25,25,25,75,75,74,74,74,75,25,25,25,25,25,25,25,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,146,75,75,75,74,74,75,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,25,146
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,146,146,74,74,74,74,146,146,146,146,74,74,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,74,146,146,146,75,75,74,74,146,74,75,25,25,75,74,75,146,25,146,146,25,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,25,146,146,25,146,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,25,74,75,75,25,25,25,25,25,25,25,75,75,75,74,25,75,25,25,25,25,25,25,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,74,74,74,74,74,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,25,146,146,146,146,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,25,75,74,75,75,75,74,74,74,146,146,146,75,25,25,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,75,146,146,146,146,74,75,75,146,74,75,74,74,75,75,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,146,146,25,146,146,146,25,146,146,25,25,25,25,25,25,146,25,146,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,25,25,25,25,25,25,25,25,25,25,25,25,75,75,25,25,25,25,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,25,25,25,75,74,25,146,75,74,75,74,74,75,75,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,74,74,146,146,146,74,75,74,74,74,75,146,25,74,74,25,25,25,25,25,25,25,25,25,75,75,75,75,74,25,25,25,25,25,146,74,74,146,146,146,25,75,74,25,146,25,25,75,75,75,75,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,146,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,75,74,75,74,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,74,74,74,75,74,75,75,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,75,146,25,146,75,74,75,75,75,74,75,25,75,74,25,25,25,25,25,25,25,25,74,75,74,75,75,75,25,25,25,25,25,146,75,146,146,146,25,25,25,146,146,146,25,25,74,74,74,74,74,25,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,75,75,75,75,74,75,74,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,146,74,74,146,146,146,74,74,74,75,75,74,25,75,75,25,25,25,25,25,25,75,74,74,74,74,74,74,74,25,25,74,25,75,75,75,25,146,146,146,25,146,146,146,146,25,25,74,75,74,74,74,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,75,146,74,74,75,75,75,74,25,25,75,74,74,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,146,146,146,74,146,146,25,74,74,75,75,74,74,74,74,74,25,25,25,25,25,75,75,74,75,75,75,74,75,25,74,75,75,75,74,74,146,146,146,146,146,146,25,146,146,25,25,25,75,75,75,75,74,74,74,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,75,75,75,74,74,75,25,25,25,74,75,146,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,74,75,74,75,74,74,25,25,25,25,25,25,25,75,75,74,74,75,75,74,75,25,75,75,74,74,74,25,25,146,146,146,146,146,146,146,25,25,25,25,74,75,74,74,75,75,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,25,146,74,74,75,74,75,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,75,75,75,146,146,146,146,75,74,74,75,74,74,75,74,25,25,75,25,75,75,75,74,74,74,74,74,74,74,25,74,75,25,75,75,75,25,25,25,25,146,25,25,25,25,25,25,146,74,146,75,74,74,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,25,146,25,146,146,25,25,25,25,146,146,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,75,75,75,75,74,75,25,75,74,75,25,25,25,25,25,75,74,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,25,146,25,25,25,25,25,25,25,74,75,146,146,146,146,75,75,75,75,74,75,74,75,25,25,74,74,74,75,74,75,25,75,75,75,75,25,25,74,75,74,74,74,75,25,25,25,25,25,25,25,25,25,25,75,74,74,74,74,75,74,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,25,146,25,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,75,75,75,74,74,75,74,75,75,74,75,75,25,75,25,75,75,74,74,74,25,25,25,146,146,146,25,146,25,25,25,25,25,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,25,146,146,25,25,25,25,25,75,75,74,25,146,146,146,146,75,75,74,74,74,75,25,25,25,75,74,75,75,75,75,25,75,75,25,25,146,74,75,75,75,146,146,75,25,25,25,25,25,25,25,25,25,74,75,75,75,146,74,75,75,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,146,146,146,146,146,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,75,74,74,75,74,74,74,75,75,75,75,75,25,75,75,75,74,25,25,25,25,25,25,25,25,146,146,25,25,25,25,25,25,146,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,75,75,75,74,25,75,74,75,25,146,146,146,146,146,75,75,75,74,75,75,75,25,75,75,75,75,25,25,25,25,25,25,25,146,74,75,75,75,75,146,146,146,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,74,74,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,25,146,146,25,146,146,146,146,25,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,74,75,75,75,74,75,74,74,75,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,146,74,74,25,74,25,75,75,75,75,25,25,25,146,75,74,75,75,74,74,74,74,75,74,74,75,25,25,25,25,25,25,25,146,74,75,75,74,74,146,25,146,146,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,146,146,146,146,25,146,146,25,146,146,146,25,146,25,25,25,146,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,146,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,75,74,74,75,74,75,74,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,25,146,146,74,75,74,74,74,25,75,74,75,25,25,25,25,74,75,75,74,75,75,74,25,74,74,75,75,25,25,25,75,74,74,146,75,75,75,75,146,74,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,74,74,74,74,75,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,75,75,74,75,75,74,75,75,75,25,25,25,75,74,75,74,75,74,75,74,25,25,25,75,74,74,74,75,75,74,74,75,75,75,75,75,75,74,75,75,146,25,146,146,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,25,146,146,25,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,74,74,74,75,75,74,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,25,25,25,74,75,74,75,75,74,75,74,25,25,25,74,74,75,25,75,74,74,75,74,74,25,25,25,25,25,75,74,74,75,25,74,74,75,74,75,146,75,74,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,75,25,74,75,74,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,74,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,74,74,75,74,74,75,75,75,25,25,25,25,25,74,74,25,74,75,74,75,74,25,25,25,25,25,25,74,74,74,146,25,74,74,74,25,75,146,146,146,146,146,25,146,146,25,25,25,25,25,146,25,25,146,146,25,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,75,25,25,25,74,146,146,146,75,74,25,146,25,25,25,25,25,25,25,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,75,75,75,74,74,74,74,74,74,25,25,25,25,25,75,74,74,75,25,25,75,25,25,25,25,75,74,75,75,25,25,146,25,146,75,74,75,146,25,146,25,146,146,146,25,25,25,146,25,146,146,146,25,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,25,25,146,146,25,146,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,146,146,74,75,146,146,74,74,74,75,75,74,74,25,25,25,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,75,75,74,75,75,75,75,74,74,25,25,25,25,25,25,25,75,25,25,25,25,25,25,25,25,75,75,74,25,25,25,146,25,146,146,146,74,74,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,25,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,146,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,25,25,25,25,25,25,25,25,25,25,74,25,74,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,146,146,146,74,75,25,74,74,146,75,74,74,74,74,74,74,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,74,74,74,74,74,25,25,25,25,25,25,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,74,146,146,25,25,146,146,25,25,146,146,146,146,146,146,146,146,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,74,75,74,146,25,25,25,25,25,25,25,25,25,74,74,74,75,74,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,74,74,75,146,74,74,75,75,25,75,74,75,25,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,75,25,25,25,25,25,25,25,25,25,25,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,74,146,25,146,146,146,25,25,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,75,75,74,146,146,146,25,25,25,25,25,25,25,74,25,25,75,75,75,74,75,75,75,25,25,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,75,146,146,25,25,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,74,75,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,74,75,74,75,146,146,146,146,25,25,25,146,146,146,25,146,146,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,146,25,25,25,25,25,25,25,25,74,74,74,146,146,146,146,146,25,25,25,25,25,74,25,25,75,75,75,74,25,25,75,74,25,25,25,146,146,146,25,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,75,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,74,146,146,146,146,25,25,25,25,25,25,25,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,74,75,74,74,146,146,146,25,25,25,25,75,75,74,25,75,75,75,74,74,25,75,74,75,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,75,25,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,146,146,75,75,74,146,25,25,146,75,74,75,74,75,75,74,25,75,75,75,75,25,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,146,146,146,146,146,146,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,146,146,146,25,146,146,25,25,25,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,146,146,74,75,74,146,74,146,75,74,74,74,25,25,74,25,74,74,75,75,25,25,25,75,25,25,25,25,74,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,25,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,75,74,75,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,75,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,25,146,146,146,146,146,25,25,146,25,25,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,146,25,146,146,146,25,146,25,25,25,146,25,146,25,146,25,146,25,25,25,25,25,25,25,25,25,146,146,75,74,146,75,75,75,74,74,75,25,25,74,25,74,74,75,25,25,25,74,75,74,75,25,25,74,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,25,74,74,25,146,146,146,146,146,146,146,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,74,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,25,25,146,146,146,146,25,146,146,146,25,146,25,146,146,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,25,146,25,25,146,146,25,146,25,25,25,25,25,146,146,25,25,146,146,75,74,75,74,74,74,74,75,74,74,25,25,25,74,25,25,74,25,25,25,25,25,75,75,74,75,75,75,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,25,25,146,25,25,25,25,25,25,146,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,75,75,75,146,74,146,146,146,25,146,146,146,25,25,25,146,146,146,146,146,146,25,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,25,25,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,146,146,146,25,146,25,146,146,146,146,146,25,25,146,25,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,25,25,25,25,146,25,25,25,25,25,146,146,146,146,146,146,146,146,75,75,25,146,75,75,75,74,75,75,25,75,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,74,74,146,146,146,146,146,146,146,146,25,25,25,146,25,25,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,75,25,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,146,25,25,25,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,146,146,146,74,74,146,146,74,75,75,75,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,25,146,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,74,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,74,75,74,75,146,146,146,25,146,146,25,25,25,146,146,146,25,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,75,74,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,146,25,146,25,25,25,25,25,25,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,146,146,146,25,25,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,146,146,25,25,75,146,146,25,25,25,74,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,25,75,74,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,74,75,74,75,25,25,25,25,25,25,146,25,25,25,25,25,25,25,74,74,74,74,74,74,74,74,74,146,25,146,25,146,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,25,146,146,146,146,146,25,146,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,25,25,146,146,146,146,146,146,146,146,146,25,146,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,25,75,74,75,25,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,75,74,75,75,74,25,25,25,25,146,146,146,146,25,146,25,25,25,25,25,74,75,75,74,75,74,75,75,75,74,146,146,25,146,146,25,146,146,146,146,25,25,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,74,75,146,25,146,25,146,146,25,25,25,146,25,25,25,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,74,25,25,25,74,25,25,146,146,146,25,146,146,146,74,75,25,25,25,25,75,75,74,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,75,74,75,74,75,75,75,74,74,146,146,146,146,146,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,75,74,146,75,75,75,25,25,25,75,74,75,74,74,75,75,75,75,74,75,146,74,74,75,146,146,25,25,146,25,146,146,25,25,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,25,25,146,146,146,25,146,146,146,146,146,146,25,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,74,25,74,75,75,25,25,146,146,25,74,75,75,75,75,75,74,75,25,74,74,74,75,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,25,25,74,74,75,75,75,74,75,75,74,74,146,146,146,146,146,146,25,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,75,75,74,74,74,74,146,25,25,74,74,74,75,75,74,74,74,74,74,75,74,75,74,75,146,146,25,146,146,146,146,146,146,25,25,146,25,25,146,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,25,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,25,146,25,146,146,146,146,146,146,25,146,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,75,146,75,74,75,75,25,146,146,74,75,75,74,75,75,75,74,74,74,74,75,74,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,74,25,74,74,75,74,75,75,75,75,146,25,146,146,146,146,146,25,146,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,74,74,146,146,75,25,146,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,74,146,146,146,146,25,74,74,75,74,74,75,74,25,25,146,74,75,74,75,74,74,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,75,75,75,74,75,75,75,74,146,74,75,74,75,74,75,74,75,75,74,74,74,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,25,146,146,25,25,25,75,74,74,74,146,146,74,75,74,25,25,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,74,74,74,74,74,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,146,146,146,146,146,146,75,74,74,74,74,74,74,25,74,74,75,75,75,74,74,25,146,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,146,146,146,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,146,25,146,146,75,74,75,74,74,74,75,25,75,74,74,75,74,75,75,75,75,75,74,74,74,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,25,25,25,74,75,74,75,75,75,146,25,146,146,146,146,146,146,146,25,146,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,75,25,25,74,74,25,25,25,146,25,25,25,25,25,25,25
|
||||||
|
25,25,25,146,146,146,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,75,74,75,74,75,74,75,25,75,75,75,74,74,75,75,146,146,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,146,146,146,25,146,146,146,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,146,146,146,25,25,25,25,25,25,25,25,146,146,25,146,74,74,75,74,75,74,74,74,74,75,75,75,25,75,75,75,74,74,74,74,74,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,75,74,25,25,25,146,25,25,25,146,25,146,146,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,75,75,25,25,25,25,146,146,146,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,25,146,146,75,74,74,75,74,25,25,25,74,75,74,74,146,74,146,146,146,146,146,25,146,25,146,146,146,146,146,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,25,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,146,146,25,25,25,25,25,25,25,25,146,25,146,75,146,146,75,74,75,75,74,75,74,74,75,25,74,75,75,75,74,75,146,146,146,146,25,25,146,146,146,146,146,146,146,25,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,146,146,146,146,75,74,146,146,146,146,146,75,74,75,25,25,25,25,25,146,146,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,75,74,74,74,74,74,75,25,25,25,74,25,25,146,25,146,146,146,146,25,25,25,25,25,146,146,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,25,25,25,25,25,146,25,146,146,25,146,146,146,146,146,146,146,146,146,25,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,75,146,146,75,146,74,75,74,75,146,74,75,75,74,74,74,74,74,75,74,74,74,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,146,146,75,74,25,146,146,146,25,75,75,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,74,74,25,74,75,74,75,74,25,146,75,25,25,25,25,25,146,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,146,146,25,25,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,146,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,146,74,75,75,74,74,74,146,25,146,146,75,74,75,74,74,25,75,74,74,75,74,75,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,74,75,75,74,25,25,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,25,146,146,75,74,75,75,75,75,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,75,75,74,74,75,74,146,25,146,146,146,74,74,75,74,25,25,25,74,74,74,75,74,74,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,75,74,74,74,74,25,25,25,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,25,146,146,146,25,74,74,74,146,74,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,146,146,146,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,74,75,74,146,146,146,74,75,74,74,74,75,25,25,146,75,75,74,75,74,75,75,146,146,146,146,146,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,74,74,74,74,74,74,25,25,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,25,25,146,25,25,25,25,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,25,146,146,146,146,25,25,146,146,146,146,146,146,146,25,25,25,25,146,146,25,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,146,146,146,146,146,25,75,74,75,75,74,25,25,25,25,146,75,146,74,75,75,74,146,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,75,75,75,74,75,74,25,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,146,146,146,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,25,146,146,146,25,25,146,146,146,146,146,146,146,146,146,25,25,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,74,74,74,74,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,146,25,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,25,74,75,75,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,25,25,25,25,146,146,25,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,146,25,25,146,146,146,146,25,146,146,146,25,25,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,74,75,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,75,75,74,74,74,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,74,74,75,74,74,74,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,25,25,25,25,25,146,25,146,25,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,146,25,146,25,25,25,146,75,74,74,75,25,25,25,25,25,25,25,25,146,146,146,146,146,74,74,74,25,75,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,25,25,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,75,75,74,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,25,25,75,75,75,75,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,75,75,146,75,25,25,25,25,25,25,25,75,25,25,146,146,25,74,75,75,25,74,74,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,146,146,146,25,25,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,75,74,75,75,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,74,74,74,75,25,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,146,25,74,25,25,74,74,74,74,74,74,75,74,75,74,75,74,74,75,74,75,75,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,74,74,74,146,25,25,25,25,25,146,25,25,146,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,25,146,146,25,25,25,25,25,74,146,25,146,25,146,146,146,146,146,146,146,146,146,146,25,146,146,75,75,75,25,25,25,25,74,74,75,74,75,75,74,74,25,75,75,75,74,74,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,25,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,74,74,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,75,25,75,25,25,25,25,74,74,74,74,74,75,74,74,74,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,146,146,146,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,25,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,74,74,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,74,75,25,25,75,25,25,75,75,74,74,75,75,25,25,25,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,146,25,146,146,25,25,25,25,146,146,146,25,25,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,146,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,75,75,75,146,146,146,146,25,146,146,146,146,146,146,146,25,146,74,75,74,75,74,25,25,25,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,25,25,25,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,25,74,25,25,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,146,146,146,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,75,74,74,146,146,146,146,146,146,146,146,146,146,146,146,146,146,74,75,75,75,74,25,25,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,25,146,146,25,146,146,146,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,25,25,74,75,25,74,75,74,75,25,74,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,146,146,146,146,25,25,146,25,146,25,146,146,146,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,146,146,146,75,75,75,146,146,146,146,25,146,146,146,146,146,146,146,25,25,25,25,75,75,75,74,75,74,74,74,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,25,146,25,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,74,75,75,74,75,25,25,75,75,75,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,146,146,146,146,146,75,75,74,146,146,146,146,146,25,146,146,146,146,25,146,146,146,146,146,146,25,75,75,75,75,25,25,25,25,25,25,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,146,146,25,25,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,74,75,74,75,25,25,25,74,75,74,74,75,75,25,25,75,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,74,75,75,25,146,146,146,146,146,25,146,146,146,25,146,146,25,146,146,146,146,75,75,74,74,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,25,25,146,146,146,146,146,146,146,146,25,25,146,146,146,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,75,75,75,75,74,75,25,25,25,74,74,74,75,75,74,25,146,75,74,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,146,25,25,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,146,146,25,146,74,146,146,146,146,146,146,25,146,146,146,146,146,146,146,25,146,146,146,146,75,74,75,75,25,25,25,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,146,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,146,146,25,146,25,25,25,146,146,146,25,146,25,146,146,146,146,146,25,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,75,75,74,74,25,25,25,25,25,75,74,74,75,74,75,75,74,75,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,146,146,146,25,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,146,25,25,146,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,146,146,146,74,146,146,146,146,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,74,74,75,75,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,146,146,25,146,146,25,25,25,146,146,146,25,146,25,146,146,146,146,146,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,75,74,75,25,25,25,25,25,25,75,74,75,75,146,146,146,74,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,25,146,25,146,25,146,146,25,146,146,25,146,25,25,25,25,25,146,146,146,25,25,146,146,146,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,146,75,74,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,75,74,75,75,25,25,74,25,25,25,146,25,25,25,25,25,25,25,25,146,25,146,146,146,146,25,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,25,146,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25
|
||||||
|
25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,25,74,75,75,25,25,25,25,25,25,74,75,74,74,75,74,74,74,25,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,74,75,75,25,146,146,146,146,146,25,146,146,146,146,146,146,146,146,74,75,75,146,146,146,74,75,146,25,146,146,146,25,25,25,25,25,146,146,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,25,25,146,146,146,146,146,146,146,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,25,74,75,75,25,25,25,25,25,74,74,74,146,75,25,75,75,75,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,146,25,146,146,25,25,25,25,25,146,146,146,146,25,146,146,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,75,74,75,74,74,74,74,74,74,74,75,75,74,75,146,146,146,146,146,146,146,146,146,146,25,25,146,74,146,25,146,146,146,74,74,75,146,146,146,146,25,25,25,25,146,146,146,146,25,146,146,146,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,25,25,25,146,146,146,146,25,25,25,146,146,146,146,25,146,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,75,74,75,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,25,25,25,25,25,25,146,25,146,25,25,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,146,25,25,25,25,25,25,25,146,146,146,146,146,25,25,146,146,25,146,146,25,146,146,146,25,25,25,25,25,25,25,74,25,25,25,25,75,74,75,74,74,74,75,75,74,25,25,146,146,146,146,146,146,146,146,25,25,146,146,146,25,146,146,146,75,75,75,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,25,146,25,25,25,25,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,75,74,75,75,74,74,75,25,25,25,25,25,25,25,146,25,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,25,146,146,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,25,25,146,146,146,146,146,146,25,146,25,25,146,25,146,75,75,25,25,146,25,146,74,75,75,75,74,75,74,146,146,146,25,146,146,146,146,25,146,25,146,146,146,146,146,146,146,74,75,75,75,146,146,25,146,25,25,25,25,146,146,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,146,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,75,74,25,25,25,25,25,25,25,25,25,25,25,25,75,74,25,75,74,74,75,75,75,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,146,25,25,146,146,146,146,146,146,25,25,25,146,25,146,146,74,74,25,74,75,75,25,146,74,75,74,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,75,146,146,146,146,25,25,146,146,146,146,146,25,146,25,25,146,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,146,146,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,146,146,25,25,146,146,146,25,146,146,146,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,25,146,25,25,25,25,25,25,25,75,74,75,75,75,74,25,25,25,25,25,25,25,75,74,75,74,75,75,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,25,25,146,146,146,146,146,146,146,25,25,25,146,74,74,74,75,74,74,75,74,75,74,75,25,25,25,146,25,146,25,146,146,146,146,146,146,146,146,146,25,146,146,25,146,146,146,146,146,146,75,25,146,25,25,25,25,146,146,146,146,146,146,146,146,25,146,25,25,146,146,146,25,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,146,146,146,146,25,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,74,75,25,25,74,75,75,75,25,25,74,74,74,74,75,75,75,74,74,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,75,74,74,25,75,75,25,75,75,75,25,25,25,25,25,25,146,146,146,25,146,146,146,146,25,146,146,25,146,146,146,25,146,146,146,146,146,75,74,75,74,75,75,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,75,75,74,25,25,25,74,74,74,25,74,75,25,75,25,75,74,75,74,74,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,74,74,75,74,74,74,74,75,75,75,25,25,25,25,25,25,25,25,146,25,25,146,25,146,146,146,146,146,25,25,146,146,146,146,146,146,146,25,74,75,75,74,74,74,75,146,146,146,74,75,75,146,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,75,74,25,25,25,25,74,25,75,75,75,74,25,74,74,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,75,75,74,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,146,146,146,146,146,146,146,146,25,75,75,75,75,74,75,74,146,146,75,74,74,75,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,75,75,74,74,74,25,25,25,75,25,75,75,75,74,74,75,74,74,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,74,74,75,75,75,74,74,74,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,25,75,75,75,74,75,74,74,75,25,146,25,146,75,74,146,146,146,25,25,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,25,25,75,74,75,25,25,25,75,74,74,75,25,25,25,25,25,25,75,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,146,146,25,25,25,74,75,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,74,74,74,74,74,25,75,25,146,74,146,25,146,146,146,146,146,146,146,146,146,25,146,146,25,146,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,75,75,74,74,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,25,25,75,74,25,25,25,25,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,25,25,74,75,74,25,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,74,75,74,74,74,74,75,74,75,75,146,25,146,146,146,25,146,146,25,146,146,146,146,146,146,146,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,75,75,75,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,74,74,75,25,25,74,74,75,75,74,74,75,74,75,146,25,146,146,25,146,146,146,25,146,146,146,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,25,146,146,25,25,25,25,75,74,75,74,75,25,25,25,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,74,74,74,75,75,74,74,75,75,75,74,75,146,146,146,146,146,25,25,146,146,146,146,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,25,25,146,146,25,25,75,74,75,75,74,74,75,74,75,74,25,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,25,74,74,74,75,74,74,75,74,74,74,75,75,74,75,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,74,75,74,75,75,74,75,75,75,75,74,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,75,74,74,74,74,75,74,74,74,74,75,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,146,146,146,146,25,25,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,25,25,25,25,25,75,25,75,74,74,74,74,75,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,75,74,75,75,74,75,75,75,75,74,75,75,75,74,75,74,146,146,146,146,146,146,146,146,25,25,25,25,25,146,146,146,25,146,146,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,75,74,75,75,75,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,75,75,74,74,74,75,75,75,25,75,75,75,74,74,74,74,146,146,146,146,146,146,146,25,146,25,25,25,25,25,146,146,146,146,146,146,146,25,25,146,146,146,146,25,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,74,75,74,74,75,74,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,74,74,75,75,75,74,75,75,74,75,74,75,75,75,25,75,146,146,146,146,146,146,146,146,146,146,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,146,25,146,25,25,25,25,25,25,25,75,74,75,25,74,75,74,75,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,146,146,25,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,146,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,75,74,75,74,74,74,25,75,74,74,75,75,75,74,25,146,146,146,146,146,25,146,146,146,146,146,146,25,25,25,146,146,25,146,146,146,146,146,25,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,74,74,74,25,25,75,75,25,75,75,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25
|
||||||
|
146,146,146,146,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,25,146,146,146,146,146,25,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,74,146,146,75,74,75,75,74,75,74,75,75,146,146,146,25,146,146,25,25,25,146,146,146,25,25,25,25,25,146,146,25,146,146,146,146,25,25,25,146,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,75,25,25,25,25,25,25,25,75,75,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146
|
||||||
|
146,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,74,75,146,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,25,146,146,146,146,146,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,74,75,25,25,25,146,146,25,25,146,146,25,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146
|
||||||
|
146,146,25,146,146,146,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,75,75,75,75,74,25,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,25,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,25,25,25,25,25,25,146,146,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,146,146,146,146,146,146
|
||||||
|
146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,75,74,75,75,74,74,74,146,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,25,25,25,25,25,146,146,146,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,146,25,25,25,25,146,25,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,146,25,25,75,74,74,74,25,74,75,75,74,75,25,25,25,25,25,25,146,146,146,146,146,25,146,25,25,25,25,146,146,146,146,146
|
||||||
|
146,146,146,146,146,146,146,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,75,75,75,74,75,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,25,25,25,25,25,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,146,25,146,146,146,146,25,146,146,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,75,74,25,75,25,74,75,75,75,25,75,75,25,25,25,25,25,146,25,146,25,146,146,146,146,146,25,25,25,146,25,25,146,146
|
||||||
|
25,25,25,146,25,25,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,75,146,75,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,25,146,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,146,25,25,25,146,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,75,75,74,75,25,75,25,25,74,75,74,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,146,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,146,25,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,146,146,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,75,75,74,75,146,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,74,74,74,74,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,146,25,25,146,146,146,25,146,146,146,146,146,146,146,25,146,146,146,25,25,146,146,25,25,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,146,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,146,75,74,146,146,146,146,25,25,25,25,25,25,25,146,25,25,25,146,146,25,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,146,146,25,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,74,75,146,146,25,25,25,74,74,75,75,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,25,146,146,25,25,146,146,146,146,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,146,146,146,146,25,25,146,25,146,146,25,146,146,146,146,146,25,25,146,146,25,146,146,25,25,146,146,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,146,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,75,146,25,146,146,146,25,25,25,146,25,146,146,25,146,25,146,146,146,25,146,146,25,146,146,146,25,146,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,75,75,74,74,75,75,74,74,75,75,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,146,25,146,146,146,146,146,146,146,146,25,146,146,25,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,75,146,146,146,146,146,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,75,75,74,74,75,74,74,74,74,75,74,75,74,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,25,74,74,75,75,75,146,146,146,146,25,146,146,25,75,74,75,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,25,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,146,146,146,146,146,146,25,25,146,146,146,146,25,146,146,146,25,25,146,146,146,25,146,146,146,25,146,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,74,146,146,25,146,25,25,25,146,146,25,146,146,146,146,146,146,25,146,146,146,146,25,25,146,25,146,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,75,74,74,75,74,75,75,75,74,75,74,75,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,75,74,74,74,75,74,146,146,146,25,146,146,25,74,75,74,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,75,146,146,146,146,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,74,74,25,146,25,146,146,74,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,75,75,25,146,75,74,146,75,25,146,146,146,25,74,74,74,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,146,25,146,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,146,146,146,25,146,25,146,25,146,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,146,146,146,25,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,25,25,146,25,25,146,146,146,146,146,146,146,146,146,146,25,25,146,146,146,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,74,146,146,146,25,146,146,146,146,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,25,25,25,25,146,74,75,74,146,146,25,74,75,74,75,75,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,25,146,146,146,146,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,25,25,25,25,25,75,75,74,25,25,75,74,74,74,146,146,146,146,25,25,146,146,146,146,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,25,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,25,25,25,25,25,74,75,75,25,25,25,25,74,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,146,25,25,146,146,146,146,146,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,146,146,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,75,75,25,25,25,25,25,74,74,75,75,25,25,25,75,74,25,25,25,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,146,25,25,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,75,74,74,74,25,25,25,25,74,74,74,25,25,25,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,75,74,74,74,74,146,25,25,25,74,75,75,75,75,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,146,146,25,146,146,146,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,146,75,74,75,146,25,146,25,25,74,75,74,75,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,146,146,146,146,25,25,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,146,25,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,25,25,75,74,75,75,74,146,146,25,25,25,25,75,75,75,25,74,74,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,25,146,25,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,25,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,25,25,25,25,25,74,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,146,25,25,25,25,25,25,25,74,74,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,25,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,146,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,75,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,146,146,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,25,25,146,25,146,146,146,146,146,25,25,25,146,25,146,146,146,25,25,25,25,146,146,146,146,146,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,146,146,146,146,146,25,146,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,75,75,75,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,25,146,146,146,146,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,25,25,25,25,25,25,74,74,74,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,25,146,146,146,25,25,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,25,25,25,25,146,25,146,146,146,146,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,75,25,74,75,75,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,25,25,25,25,146,25,146,146,146,146,146,146,146,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,74,74,74,75,75,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,25,146,25,25,25,25,146,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,146,146,25,146,146,146,25,146,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,146,146,146,25,146,25,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,25,75,74,75,74,75,74,75,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,25,25,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,74,75,75,25,75,75,74,75,25,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,25,25,25,146,25,146,146,25,146,146,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,25,25,74,74,74,75,74,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,74,75,74,74,75,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,25,25,25,146,146,25,146,146,146,146,25,146,25,25,25,25,25,146,146,25,146,146,146,146,25,146,146,25,25,25,25,25,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,25,74,74,75,74,75,75,75,74,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,25,25,75,75,25,74,75,75,74,74,75,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,25,146,146,146,25,25,146,146,146,25,25,25,25,25,146,146,146,25,146,146,146,146,25,146,25,25,25,25,146,25,146,146,146,25,25,25,25,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,75,25,74,74,75,75,74,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,75,74,74,75,74,74,25,25,74,75,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,146,25,146,146,146,146,146,146,25,146,146,25,25,25,146,146,146,146,146,146,146,25,146,25,146,25,25,25,25,25,25,146,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,74,74,25,75,74,75,75,74,75,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,74,25,75,25,75,74,75,25,25,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,146,146,146,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,25,75,74,74,74,75,74,25,75,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,74,25,146,74,74,75,25,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,146,146,146,146,146,25,146,146,25,146,146,146,25,25,25,25,25,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,74,25,25,25,25,25,25,75,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,146,25,146,146,25,25,25,25,146,146,146,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,25,146,146,75,25,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,25,25,25,25,25,25,25,75,25,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,146,146,146,146,146,25,25,146,146,146,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,75,74,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,146,146,25,146,146,25,25,25,146,146,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,75,75,146,74,74,74,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,146,146,146,146,25,25,25,25,146,25,146,146,146,146,25,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,74,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,146,146,146,25,146,25,25,25,25,25,146,146,146,146,25,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,75,74,74,74,74,74,74,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,74,75,146,146,146,74,146,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,25,25,25,25,25,146,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,75,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,146,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,25,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,146,146,146,146,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,74,74,75,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,75,25,25,74,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,25,146,146,146,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,25,146,146,25,146,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,25,25,25,74,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,146,146,146,25,146,146,25,25,25,25,25,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,146,25,25,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,74,74,74,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,146,146,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,74,74,75,75,75,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,74,25,25,25,25,25,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,25,25,146,25,25,25,25,25,74,75,74,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,75,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,75,74,75,74,25,25,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,74,74,75,75,74,74,74,146,146,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,74,25,25,25,25,25,74,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,75,74,75,75,75,74,75,74,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,74,74,75,25,25,75,25,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,74,74,74,146,74,75,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,75,74,75,75,75,25,74,74,25,25,75,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,146,25,146,146,25,146,146,25,25,25,25,25,75,75,74,25,74,75,75,75,74,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,146,146,146,146,146,25,25,25,74,25,74,74,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,25,146,146,146,25,146,146,146,146,25,25,25,25,25,25,146,25,146,75,75,25,146,75,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,74,74,74,74,75,75,75,75,75,74,25,25,25,25,25,146,146,146,146,25,146,25,25,146,146,146,146,146,25,146,146,146,146,25,25,146,146,146,146,25,25,146,146,25,25,74,75,75,75,74,74,75,74,74,74,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,25,74,75,74,75,75,146,146,75,74,146,146,25,146,146,25,25,25,25,25,25,146,146,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,146,146,146,146,146,74,146,146,25,146,74,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,25,146,146,146,25,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,74,75,74,75,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,25,25,25,25,75,74,74,25,75,74,75,75,74,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,25,74,74,75,74,75,25,74,74,74,25,146,25,146,146,25,25,25,25,25,25,146,146,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,146,25,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,25,146,25,25,25,146,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,75,25,25,25,74,75,25,25,25,25,25,25,25,25,146,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,146,146,146,146,146,146,25,25,25,74,25,75,74,74,74,25,74,25,25,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,146,146,146,146,146,25,25,75,74,74,75,75,146,74,75,74,75,146,146,146,146,146,25,25,25,25,146,146,146,25,146
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,25,146,146,146,25,25,25,25,146,25,146,146,146,146,25,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,25,25,25,25,75,74,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,25,25,25,25,146,146,146,25,25,25,25,25,146,146,146,146,146,25,25,25,25,75,74,75,74,25,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,74,75,74,74,74,74,146,74,74,146,146,146,25,25,25,25,25,146,146,146,146,146
|
||||||
|
146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,25,25,25,146,25,146,146,146,25,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,25,25,25,25,146,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,74,75,146,74,146,146,75,74,146,146,146,146,25,25,25,25,25,25,146,146,146,146
|
||||||
|
146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,146,146,146,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,74,25,74,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,25,146,146,25,25,25,25,25,25,25,75,75,146,74,74,75,75,25,146,25,146,146,25,25,25,25,25,25,146,146,146,146
|
||||||
|
25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,146,25,146,146,146,146,25,25,25,25,25,25,25,146,25,146,146,146,146,25,146,25,146,25,146,146,25,25,25,146,146,146,146,146,25,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,146,146,25,146,146,146,146,25,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,75,75,75,75,74,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,74,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,75,25,74,74,74,74,75,75,146,25,146,146,25,25,25,25,25,25,25,25,25,25,146,25
|
||||||
|
146,25,146,146,25,25,25,146,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,25,146,25,25,25,25,146,146,146,25,25,25,146,146,25,25,25,25,146,146,25,146,146,25,25,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,25,74,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,74,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,74,74,75,75,74,75,74,74,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,25,146,146,146,146,25,25,146,25,25,146,146,146,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,25,146,146,146,25,25,25,25,25,25,146,25,25,25,25,25,25,146,25,25,25,146,146,146,146,25,146,146,25,146,25,25,146,146,146,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,25,25,74,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,75,75,74,146,25,146,146,146,25,75,74,25,25,25,146,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,74,74,25,25,25,75,74,75,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,25,146,146,146,146,146,25,146,146,146,25,146,146,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,75,75,74,74,74,75,146,146,146,146,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,25,25,25,25,25,146,75,74,74,25,25,25,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,146,25,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,75,74,74,74,75,74,146,25,146,75,75,74,25,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,146,25,25,25,146,146,146,146,146,75,74,25,146,146,146,75,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,146,25,146,25,146,146,146,146,25,25,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,75,25,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,74,74,74,74,75,75,75,75,75,146,25,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,25,25,146,146,25,146,74,75,74,75,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
146,25,146,25,146,25,146,25,146,146,146,25,146,146,146,25,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,75,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,25,25,25,25,25,25,25,146,146,146,146,146,75,74,74,74,25,146,75,74,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,146,25,146,146,146,74,75,146,146,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,25,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,75,74,74,75,74,74,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,25,25,25,25,25,25,25,25,25,146,146,146,74,74,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,75,74,75,74,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,146,25,25,25
|
||||||
|
25,25,25,146,146,146,146,146,146,25,146,146,146,25,146,146,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,25,25,74,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,74,74,146,146,146,146,75,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,25,25,25,25,25,25,25,25,146,146,146,146,146,74,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,25,25,25,25,25,146,146,146,146,146,146,75,74,74,75,146,146,146,146,146,146,146,146,25,146,146,146,146,25,25,146,25,146,146,146,146,146
|
||||||
|
25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,25,25,25,25,146,25,146,146,146,25,146,25,146,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,25,74,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,74,75,74,74,146,146,146,75,75,75,75,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,75,74,25,25,25,25,25,25,25,146,146,146,74,75,146,146,25,146,146,25,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,146,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,25,25,25,25,146,146,146,146,146,146,146,74,74,75,74,146,146,146,146,146,146,146,25,146,25,146,146,25,25,146,146,25,146,146,25,25
|
||||||
|
25,146,146,25,146,146,25,25,146,146,146,25,25,146,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,146,25,146,146,146,146,25,146,146,25,146,146,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,25,75,75,25,25,25,25,74,75,75,25,25,25,25,25,25,25,25,146,146,74,75,74,146,146,146,25,75,75,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,74,25,75,74,25,25,25,25,25,25,25,75,75,74,146,146,146,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,146,146,146,25,146,146,25,25,25,146,25,146,146,146,146,146,25,146,146,146,25,25,146,146,146,146,146,146,146,146,146,75,75,74,75,146,146,146,146,146,146,25,25,146,25,146,146,146,146,146,25,146,146,146,146
|
||||||
|
146,146,146,146,146,146,25,146,25,25,25,25,25,25,146,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,25,25,146,146,146,146,146,146,25,146,25,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,75,25,74,74,75,74,74,75,75,74,75,75,75,75,75,25,25,25,25,146,146,146,146,146,146,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,74,75,75,75,25,25,25,75,74,75,74,74,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,146,146,146,25,146,146,146,146,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,25,146,146,146,75,75,74,74,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146
|
||||||
|
146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,25,25,25,146,146,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,74,74,75,74,74,74,75,75,75,74,25,74,74,74,74,74,25,146,146,146,25,146,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,75,75,75,74,75,25,25,25,25,75,74,74,74,74,146,25,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,25,146,25,146,146,25,146,25,25,146,146,146,146,25,146,146,146,146,146,146,25,25,146,146,146,146,146,146,74,74,146,75,74,74,74,74,75,74,146,146,146,146,146,146,146,146,25,25,146,146,146,146,146,146,146
|
||||||
|
25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,25,146,25,146,146,146,146,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,75,75,75,74,75,75,75,75,75,74,74,74,75,74,75,74,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,74,146,146,74,75,75,25,25,25,75,75,74,74,74,25,25,25,146,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,146,25,25,146,146,146,146,25,146,25,146,146,25,146,146,146,146,146,146,146,146,25,25,146,146,146,25,146,74,75,74,74,74,74,74,74,25,146,146,146,146,146,146,146,25,146,146,146,25,146,146,25,146,146,146
|
||||||
|
146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,25,25,25,25,146,146,146,25,146,146,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,25,146,146,146,146,146,146,146,146,25,146,146,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,74,25,25,25,74,74,75,74,74,25,74,75,74,75,74,74,146,146,146,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,146,25,25,25,146,146,25,146,74,75,25,25,74,74,74,74,74,74,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,74,75,146,146,146,74,74,25,25,146,25,146,146,25,146,146,146,146,146,25,25,146,25,146,146,146,25
|
||||||
|
146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,74,25,25,25,146,25,146,146,146,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,25,146,146,25,146,146,146,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,74,74,75,25,25,74,75,75,74,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,25,74,75,25,75,75,25,25,25,75,75,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,146,25,146,146,146,146,146,25,146,25,146,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,146,146,146,146,75,75,74,74,75,146,146,25,25,146,146,146,146,146,25,146,25,146,146,146,146,146,25,146,146,146
|
||||||
|
146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,74,74,74,74,25,74,75,74,74,146,146,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,25,146,146,146,25,146,146,146,146,146,146,146,146,146,25,146,25,146,146,146,146,146,146,146,25,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,25,25,25,74,74,74,75,75,146,146,146,146,146,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,25,146,146,146,146,25,146,146,146,25,146,74,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,25,25,25,146,146,146,25,146,146,146,146,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,146,25,25,25,25,146,146,146,146,25,146,146,25,146
|
||||||
|
25,25,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,25,25,25,74,75,74,75,74,74,25,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,146,146,146,146,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,25,25,25,25,75,74,74,146,146,25,25,25,25,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,74,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,25,146,25,25,25,146,146,25,146,146,146,25,146,146,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146
|
||||||
|
25,25,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,74,25,25,25,25,74,74,75,74,74,74,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,146,146,146,25,146,146,25,146,146,146,146,146,146,146,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,75,25,25,25,146,146,146,146,146,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,146,146,146,146,146,146,146,25,25,146,146,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146
|
||||||
|
25,25,25,25,25,146,25,25,25,25,74,25,25,25,25,25,25,25,25,25,25,74,75,25,75,75,74,25,25,25,75,25,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,25,146,25,146,146,25,25,25,25,25,25,146,146,25,146,146,146,146,146,25,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,75,25,25,25,25,146,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,25,146,146,25,146,146,146,25,146,25,146,146,146,25,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,146,146,146,146,146,146,25,146,146,25,146,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,74,25,25,25,25,25,25,25,25,25,75,74,75,74,74,74,75,75,75,74,25,25,25,74,74,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,25,146,146,146,146,146,146,25,146,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,146,146,146,146,25,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,146,146,146,25,146,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,75,74,75,75,25,25,74,75,75,74,75,75,75,75,75,74,25,75,74,75,74,25,25,75,74,75,74,75,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,25,146,146,146,25,25,25,25,25,25,146,25,146,146,146,146,146,146,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,74,25,25,74,75,75,75,74,74,74,25,25,25,25,25,25,25,74,75,74,75,25,25,25,25,74,74,74,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,146,146,146,25,146,25,25,146,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,25,146,146,146,25,146,146,146,146,25,146,146,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,75,75,74,25,25,25,25,25,74,74,75,75,25,25,25,74,74,25,25,146,25,146,146,25,25,146,25,25,146,146,146,146,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,146,25,25,25,25,146,146,146,146,146,146,25,146,146,25,146,25,146,25,146,25,146,146,146,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,74,75,74,75,75,25,25,25,25,25,25,25,75,25,25,75,74,74,74,75,146,146,146,25,146,146,25,146,146,25,146,146,146,146,146,146,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,146,25,146,146,146,146,146,25,25,146,146,146,146,146,146,146,146,146,25,146,25,25,25,146,25,146,146,146,25,25,25,146,146,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,74,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,74,74,74,25,146,146,146,146,25,146,146,25,146,146,146,25,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,146,146,146,146,25,146,146,146,146,146,146,25,146,146,146,25,146,146,25,25,146,25,25,146,146,25,146,25,25,146,25,25,25,146,25,25,25,25,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,146,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,25,75,74,25,25,146,146,25,146,146,146,146,146,146,146,146,25,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,25,25,25,146,146,146,146,25,146,146,25,146,25,146,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,74,75,74,25,25,75,25,146,25,146,25,146,146,146,25,146,146,146,146,146,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,25,25,25,25,146,25,146,146,25,146,25,25,146,25,146,146,146,146,25,146,146,146,146,25,25,25,25,146,146,146,146,146,146,146,146,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,74,25,74,74,74,75,74,25,75,74,25,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,25,146,25,25,25,25,25,25,25,146,25,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,146,146,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,75,75,75,74,75,74,75,74,75,74,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,25,75,75,146,146,146,146,146,146
|
||||||
|
25,25,25,25,25,25,25,25,146,146,146,25,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,75,74,74,74,75,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,146,74,146,74,74,146,146,146,25,146,25
|
||||||
|
25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,75,75,74,75,75,74,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,146,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,74,74,75,75,146,146,146,25,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,74,75,75,74,74,146,146,146,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,146,146,146,146,25,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,25,146,146,146,146,146,25,146,146,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,74,74,74,74,146,25,146,146,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,75,74,74,146,74,146,146,146,75,74,25,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,146,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,75,74,146,146,146,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,75,74,74,74,74,75,74,75,75,146,146,146,75,74,75,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,74,25,146,146,146,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,146,25,75,74,74,75,74,74,75,74,75,75,75,25,146,146,146,74,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,146,75,146,146,74,146,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,74,74,75,74,74,146,74,75,74,75,75,146,74,25,146,146,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,25,74,75,75,75,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,74,75,74,146,75,74,74,146,146,75,74,74,74,146,25,75,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,25,146,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,75,75,75,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,146,146,75,74,75,74,74,74,74,74,25,146,75,75,75,74,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,146,146,25,25,25,146,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,74,74,74,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,146,146,146,146,25,75,74,75,75,75,75,146,146,146,74,74,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,146,74,146,146,146,146,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,146,146,75,75,75,74,75,74,74,75,74,74,75,75,146,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,25,146,146,25,25,25,25,25,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,75,74,74,146,74,146,146,146,146,25,25,146,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,75,75,74,75,74,75,75,75,75,75,146,25,75,25,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,74,74,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,75,74,75,75,75,74,146,146,146,146,146,146,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,74,75,146,75,75,75,25,146,146,75,25,146,25,25,25,25,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,74,75,75,75,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,74,74,74,75,75,74,146,146,25,25,25,25,146,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,75,25,75,75,75,74,146,146,146,146,25,25,25,25,74,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,75,75,74,75,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,25,25,25,146,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,25,146,146,25,25,25,25,25,25,146,25,146,74,74,74,74,146,146,146,146,146,146,146,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,75,74,74,74,75,74,74,25,25,74,75,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,25,25,25,146,25,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,75,75,74,74,75,74,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,25,25,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,146,146,146,75,74,74,74,146,146,146,146,146,146,146,146,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,74,146,25,25,74,25,74,74,25,74,25,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,25,146,25,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,74,74,75,74,74,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,25,146,25,146,146,146,146,146,146,25,25,25,146,146,146,75,74,75,146,146,146,146,75,146,74,74,74,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,75,74,25,25,74,74,75,75,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,146,25,146,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,75,75,74,75,75,75,74,74,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,146,146,75,74,74,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,25,146,146,146,146,25,146,25,25,25,25,146,146,25,146,75,74,146,146,146,146,75,75,74,146,74,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,25,74,25,25,75,25,75,75,75,74,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,74,74,75,74,75,75,75,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,74,75,74,74,75,74,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,74,74,75,146,74,75,74,75,75,74,75,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,25,25,25,146,25,146,146,25,146,25,25,146,146,146,25,25,74,75,75,74,75,25,74,75,74,25,25,25,25,25,25,25,25,25,146,25,146,25,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,74,74,74,74,75,74,74,146,25,75,74,74,75,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,74,74,75,74,75,74,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,146,146,146,146,25,25,25,25,146,146,25,146,146,146,74,74,74,75,75,74,74,74,25,74,74
|
||||||
|
25,25,25,25,25,146,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,146,25,146,146,146,25,146,146,25,25,25,25,75,74,75,75,25,25,25,74,75,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,74,75,75,75,146,75,74,146,146,75,75,74,75,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,75,74,75,74,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,25,25,25,25,74,75,75,74,75,75,75,74,75,75,74,75,75,74,74,75,25,74,75
|
||||||
|
25,25,146,146,146,146,25,146,146,25,25,25,146,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,74,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,25,25,146,146,146,146,146,146,25,146,25,25,146,25,25,25,25,25,25,25,25,25,25,146,75,75,74,75,74,146,75,75,74,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,74,74,75,74,74,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,75,74,74,74,75,75,75,75,75,74,74,75,75,75,74,75,74,75,74
|
||||||
|
25,25,146,25,146,146,146,146,146,146,25,25,25,146,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,146,25,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,146,25,146,146,25,146,146,25,25,25,146,25,25,25,25,25,25,25,25,25,25,146,74,74,75,74,74,146,75,74,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,74,75,74,146,75,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,146,75,74,75,74,75,75,74,74,74,74,74,146,146,74,75,75,75,75,25
|
||||||
|
146,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,146,25,146,146,146,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,25,25,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,146,146,74,75,74,75,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,75,75,74,74,75,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,25,25,25,25,25,25,146,146,74,74,74,75,74,146,74,74,74,74,146,75,75,25,74,74,75,74
|
||||||
|
146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,25,146,146,146,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,146,146,25,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,146,25,146,74,74,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,75,74,74,25,146,146,25,146,146,146,146,25,146,25,25,146,146,25,25,25,25,25,146,146,25,25,146,146,146,25,25,25,25,146,146,146,25,146,25,25,25,75,75,75,74,75,75,75,74,75,75,75,75,75,74
|
||||||
|
25,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,75,74,74,146,146,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,75,25,25,74,75,74,146,146,146,146,146,146,146,146,146,25,146,25,146,146,146,146,25,25,25,25,146,146,25,146,25,146,146,146,146,25,25,25,25,146,146,25,25,25,25,146,74,75,75,75,74,146,146,74,25,74,75,74,75,75
|
||||||
|
25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,25,25,25,25,146,25,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,74,75,75,74,74,74,146,146,25,146,146,25,146,146,25,25,146,146,25,25,146,146,146,25,25,146,25,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,146,74,74,75,146,146,25,146,75,25,74,75,74,25,25
|
||||||
|
146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,146,25,25,25,146,146,25,146,146,146,146,25,25,25,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,75,74,75,74,75,74,146,146,25,146,146,146,146,25,25,25,25,146,146,146,146,146,146,146,25,146,146,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,75,74,75,25,146,146,146,74,74,25,74,74,74,25,25
|
||||||
|
146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,25,146,25,25,25,25,25,25,25,25,146,146,25,146,146,25,146,146,146,25,25,25,25,25,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,74,75,74,75,75,74,75,74,75,74,74,25,25,25,25,25,25,75,74,74,75,75,74,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,146,25,146,25,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,75,75,74,25,25,25,25,25,75,74,25,25,74,74,74,25
|
||||||
|
146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,75,75,146,75,74,75,75,74,75,74,75,74,25,25,74,74,74,25,75,75,75,74,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,74,75,25,25,25,25,74,75,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,25,25,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,74,146,75,75,74,75,74,75,75,75,74,74,74,75,74,74,74,74,25,74,74,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,25,25,146,146,146,146,25,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,25,25,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,146,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,74,75,75,74,74,74,74,75,74,75,75,74,25,25,25,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,146,25,146,25,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,25,25,25,25,25,25,25,25
|
||||||
|
25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,75,74,75,146,25,25,146,75,74,74,74,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,25,25,25,25,25,25,25,25
|
||||||
|
25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,146,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,74,75,75,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,146,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,25,25,25,25,146,146,25,25,146,146,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,25,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,25,146,146,25,146,25,146,146,146,146,25,25,25,146,146,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,146,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,146,25,146,146,146,146,146,146,146,146,146,25,146,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,74,25,25,25,25,25,146,25,25,25,25,25
|
||||||
|
146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,146,146,146,146,146,146,146,146,25,146,146,146,25,25,146,25,146,25,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,25,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,25,25,146,146,25,146,146,25,146,25,25
|
||||||
|
25,146,146,25,25,25,25,146,146,146,25,146,146,146,25,146,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,75,74,25,146,146,146,146,146,25,146,146,146,25
|
||||||
|
25,146,146,146,146,146,146,146,25,146,146,146,25,146,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,146,25,25,25,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,25,25
|
||||||
|
25,146,146,146,146,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,146,25,25,25,25,146,146,146,25,146,25,25,146,25,146,25,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,25
|
||||||
|
25,25,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,146,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,25,25,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,146,146,146,146
|
||||||
|
25,25,25,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,146,146,25,146,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,146,146,25,146,146,25,146,25,146,146,146,74,75,74,75,74,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,25
|
||||||
|
25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,74,74,146,146,146,146,75,75,74,74,75,25,25,25,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25
|
||||||
|
25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,25,146,146,146,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,146,75,75,74,146,146,75,74,74,75,74,74,25,25,25,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,146,146,146,146,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,74,146,74,74,74,75,75,74,75,75,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,75,74,75,75,75,74,146,74,74,74,74,75,74,74,74,74,75,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,146,25,25,146,25,146
|
||||||
|
25,25,146,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,74,74,74,75,74,25,25,75,75,25,75,75,75,74,74,75,75,75,74,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,146,25,25,146,146,146,146
|
||||||
|
25,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,146,25,25,25,25,25,25,75,74,74,25,25,25,25,25,25,25,75,74,75,75,25,74,75,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,146,146,146,146,146,25,146,146
|
||||||
|
146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,25,146,25,146,146,25,25,25,25,74,25,25,25,25,25,25,25,25,25,25,75,25,74,75,75,74,74,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,146,25,25,146,25,146,146,25,146,25,146
|
||||||
|
146,146,146,25,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,146,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,75,25,74,74,25,75,75,74,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146
|
||||||
|
25,146,146,146,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,146,146,146,146,146,146,146,146,146,25,25,146,146,146,146,146,146,25,146,25,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,74,74,75,75,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146
|
||||||
|
146,146,25,25,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,25,25,146,146,146,25,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,74,75,74,75,74,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,25
|
||||||
|
146,146,146,146,25,146,146,25,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,25,25,146,146,146,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,74,75,74,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,25,146,146,146,146,146,146,146,146,146,146,146
|
||||||
|
25,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,75,75,25,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,146,146,146,146,146,146,146,146,146,146,146
|
||||||
|
146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,146,146,146,25,146,146,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,146,146,146
|
||||||
|
25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,25,146,146,146,146,146,146,146,25,146,146,146,146,146,25,25,146,146,146,146,146,146
|
||||||
|
25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,25,146,146,25,146,25,146,146,25,146,146,146,146,146,146,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,25,25,25,25,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,146,25,146,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,25,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,146,146
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,25,146,146,146,146,25,146,25,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,25,146,146,146,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,25,25,25,25,25,25,25,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,146
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,25,146,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,146,25,25,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,146,146,146,25,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,146,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,25,25,146,25,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,25,146,146,25,146,25,146,146,146,25,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,25,25,25,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,25,25,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,146,146,146,25,146,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,25,146,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,25,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,146,25,146,146,146,146,146,146,146,146,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,25,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,25,146,146,25,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,146,146,146,146,25,146,146,25,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,25,146,25,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,25,146,146,25,25,25,146,146,25,146,25,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,146,25,146,25,146,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,146,25,25,25,25,25,25,25,25,146,146,146,146,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,25,25,25,146,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,146,146,25,146,25,146,25,25,146,146,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,25,146,146,146,146,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,25,25,25,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,25,146,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,146,25,146,25,146,146,25,146,146,146,146,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,146,146,25,146,146,146,146,146,146,146,146,146,146,25,146,146,25,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,146,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,25,146,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,146,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,25,146,146,146,146,25,25,25,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,25,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
|
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,25,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,146,146,146,25,25,25,25,25,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,146,146,146,25,146,25,146,25,146,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 127 B |
Binary file not shown.
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
@@ -1,9 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "client"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
shared = { path = "../shared" }
|
|
||||||
pbio = { git = "https://codeberg.org/irrlicht/rust-pbio.git" }
|
|
||||||
bytemuck = { version = "1.24.0", features = ["derive"] }
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
pub struct Image {
|
|
||||||
pub width: u32,
|
|
||||||
pub height: u32,
|
|
||||||
pub pixels: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Image {
|
|
||||||
/// Load an 8-bit indexed TGA (type 1 or type 9) from disk.
|
|
||||||
pub fn from_tga(path: &str) -> Self {
|
|
||||||
let mut file = File::open(path).unwrap();
|
|
||||||
let mut data = Vec::new();
|
|
||||||
file.read_to_end(&mut data).unwrap();
|
|
||||||
|
|
||||||
let image_type = data[2];
|
|
||||||
let width = u16::from_le_bytes([data[12], data[13]]) as u32;
|
|
||||||
let height = u16::from_le_bytes([data[14], data[15]]) as u32;
|
|
||||||
let colormap_entries = u16::from_le_bytes([data[5], data[6]]) as usize;
|
|
||||||
let colormap_entry_bytes = data[7] as usize / 8;
|
|
||||||
let pixel_offset = 18 + colormap_entries * colormap_entry_bytes;
|
|
||||||
|
|
||||||
let pixel_count = (width * height) as usize;
|
|
||||||
let mut pixels = vec![0u8; pixel_count];
|
|
||||||
match image_type {
|
|
||||||
1 => pixels.copy_from_slice(&data[pixel_offset..pixel_offset + pixel_count]),
|
|
||||||
9 => decode_rle(&data[pixel_offset..], &mut pixels),
|
|
||||||
_ => panic!("unsupported TGA image type: {}", image_type),
|
|
||||||
}
|
|
||||||
|
|
||||||
Image { width, height, pixels }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Split this image into 8×8 tiles, row-major. Tile ID = flat index into the Vec.
|
|
||||||
pub fn to_tileset(&self) -> Vec<[u8; 64]> {
|
|
||||||
let w = self.width as usize;
|
|
||||||
let cols = w / 8;
|
|
||||||
let rows = self.height as usize / 8;
|
|
||||||
let mut tiles = Vec::with_capacity(rows * cols);
|
|
||||||
for row in 0..rows {
|
|
||||||
for col in 0..cols {
|
|
||||||
let mut tile = [0u8; 64];
|
|
||||||
for tr in 0..8 {
|
|
||||||
for tc in 0..8 {
|
|
||||||
tile[tr * 8 + tc] = self.pixels[w * (row * 8 + tr) + col * 8 + tc];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tiles.push(tile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tiles
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_rle(src: &[u8], dst: &mut [u8]) {
|
|
||||||
let mut si = 0;
|
|
||||||
let mut di = 0;
|
|
||||||
while di < dst.len() {
|
|
||||||
let header = src[si];
|
|
||||||
si += 1;
|
|
||||||
let count = (header & 0x7f) as usize + 1;
|
|
||||||
if header & 0x80 != 0 {
|
|
||||||
dst[di..di + count].fill(src[si]);
|
|
||||||
si += 1;
|
|
||||||
} else {
|
|
||||||
dst[di..di + count].copy_from_slice(&src[si..si + count]);
|
|
||||||
si += count;
|
|
||||||
}
|
|
||||||
di += count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,560 +0,0 @@
|
|||||||
mod pathfind;
|
|
||||||
mod pixelhelper;
|
|
||||||
|
|
||||||
use std::collections::{HashMap, VecDeque};
|
|
||||||
|
|
||||||
use crate::assets::Image;
|
|
||||||
use crate::input::{GameAction, InputState};
|
|
||||||
use crate::net::{EntityInfo, NetClient, NetEvent};
|
|
||||||
use shared::{chunk_id, delta_action, player_action, step_allowed, tile_collidable, TICKS_PER_MOVE};
|
|
||||||
|
|
||||||
pub enum GameSignal {
|
|
||||||
Quit,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The server's movement cadence (24 Hz base tick, movement on every 4th tick
|
|
||||||
/// → 6 Hz ≈ 167 ms/tile). Entity interpolation lerps over this interval.
|
|
||||||
const MOVE_INTERVAL_MS: usize = 167;
|
|
||||||
|
|
||||||
/// Length of one server tick. Steps are not paced by a local timer but scheduled onto
|
|
||||||
/// the server's tick timeline, estimated from the last `StatePacket` plus elapsed time.
|
|
||||||
const TICK_MS: f32 = 1000.0 / shared::TICK_HZ as f32;
|
|
||||||
|
|
||||||
/// Length of one movement window.
|
|
||||||
const WINDOW_MS: f32 = TICK_MS * TICKS_PER_MOVE as f32;
|
|
||||||
|
|
||||||
/// Safety margin on top of the measured RTT when choosing the scheduling lead — covers
|
|
||||||
/// jitter and the server answering pings on its tick grid.
|
|
||||||
const LEAD_MARGIN_MS: f32 = 50.0;
|
|
||||||
|
|
||||||
/// RTT sampling interval; the measurement drives the scheduling lead.
|
|
||||||
const PING_INTERVAL_MS: usize = 1000;
|
|
||||||
|
|
||||||
/// Upper bound on queued-but-unconfirmed steps — purely a prediction bound: window
|
|
||||||
/// addressing already caps what the server will hold (windows are consecutive and only
|
|
||||||
/// `sched_lead` ahead), while confirmations lag a full RTT behind, so on a slow link
|
|
||||||
/// several correct steps are legitimately in flight at once. Steps whose window the
|
|
||||||
/// acknowledgment cursor has passed expire automatically, so the path can never go
|
|
||||||
/// permanently stale.
|
|
||||||
const PATH_MAX_LEN: usize = 8;
|
|
||||||
|
|
||||||
/// Palette index for the path indicator. Pure blue in RGB332 (r=0, g=0, b=3).
|
|
||||||
const PATH_COLOR: u8 = 0b000_000_11;
|
|
||||||
|
|
||||||
/// Dimmer blue for planned-but-not-yet-sent route tiles (r=0, g=0, b=2).
|
|
||||||
const ROUTE_COLOR: u8 = 0b000_000_10;
|
|
||||||
|
|
||||||
/// Viewport geometry: 30×30 tiles of 8 px, top-left of the framebuffer.
|
|
||||||
const VIEW_TILES: i32 = 30;
|
|
||||||
const TILE_PX: i32 = 8;
|
|
||||||
const VIEW_PX: i32 = VIEW_TILES * TILE_PX;
|
|
||||||
|
|
||||||
/// Camera pan speed. The player walks 8 px per 167 ms ≈ 48 px/s; the camera is a bit
|
|
||||||
/// faster, so it trails during movement and settles right after the player stops.
|
|
||||||
const CAM_SPEED: f32 = 64.0; // px/s
|
|
||||||
|
|
||||||
/// A position correction farther than this is a teleport — snap instead of panning.
|
|
||||||
const CAM_SNAP_PX: f32 = 96.0;
|
|
||||||
|
|
||||||
/// Render-side smoothing state for one entity: the previously confirmed tile and how
|
|
||||||
/// long ago the current one was confirmed. Positions are tiles; rendering lerps between
|
|
||||||
/// them in pixels over one movement interval.
|
|
||||||
struct EntityLerp {
|
|
||||||
prev: (i32, i32),
|
|
||||||
cur: (i32, i32),
|
|
||||||
t_ms: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// One sent-but-unacknowledged step: the movement window it is scheduled for and its
|
|
||||||
/// *delta* — deliberately not an absolute tile. The server executes deltas, so the
|
|
||||||
/// prediction is derived by replaying pending deltas on top of the last authoritative
|
|
||||||
/// position: a server surprise shifts the whole prediction instead of invalidating it.
|
|
||||||
/// The window is both the retraction address and the expiry key against the ack cursor.
|
|
||||||
struct PathStep {
|
|
||||||
window: u32,
|
|
||||||
delta: (i32, i32),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Game {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
tileset: Vec<[u8; 64]>,
|
|
||||||
entity_tileset: Vec<[u8; 64]>,
|
|
||||||
net: NetClient,
|
|
||||||
player_entity_id: u32,
|
|
||||||
/// Authoritative position, as last confirmed by the server.
|
|
||||||
player_pos: (i32, i32),
|
|
||||||
/// Queued future steps: sent to the server but not yet confirmed. Shown as the
|
|
||||||
/// planned path; the head is consumed as the server confirms each move.
|
|
||||||
path: VecDeque<PathStep>,
|
|
||||||
/// Planned route from click-to-move: tiles not yet sent to the server. Fed into
|
|
||||||
/// `path` one step per movement window. Keyboard input cancels it.
|
|
||||||
route: VecDeque<(i32, i32)>,
|
|
||||||
/// The persistent movement goal. Outlives the route: any surprise (blocked step,
|
|
||||||
/// server divergence) triggers a replan toward it, and while the mouse is held it
|
|
||||||
/// tracks the tile under the cursor. Cleared on arrival, unreachability, keyboard
|
|
||||||
/// override, or a click on an unreachable tile.
|
|
||||||
goal: Option<(i32, i32)>,
|
|
||||||
/// Tick clock: the last tick announced by a `StatePacket` and how long ago it
|
|
||||||
/// arrived — together an estimate of the server's current tick.
|
|
||||||
server_tick: u32,
|
|
||||||
tick_age_ms: usize,
|
|
||||||
/// Smoothed round-trip time from the automatic pings; `0` until the first pong.
|
|
||||||
rtt_ms: f32,
|
|
||||||
ping_accum_ms: usize,
|
|
||||||
/// Highest movement window a step was scheduled for. At most one step is scheduled
|
|
||||||
/// per window; retraction lowers this so freed windows can be re-addressed.
|
|
||||||
last_sched_window: u32,
|
|
||||||
/// Viewport top-left in world pixels. Follows the player linearly instead of
|
|
||||||
/// snapping tile-to-tile; only rendering rounds it to whole pixels.
|
|
||||||
cam: (f32, f32),
|
|
||||||
entities: Vec<EntityInfo>,
|
|
||||||
/// Per-entity interpolation state, keyed by entity id. Purely cosmetic — all game
|
|
||||||
/// logic keeps using the authoritative tile positions.
|
|
||||||
lerp: HashMap<u32, EntityLerp>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Game {
|
|
||||||
pub fn start(server_addr: std::net::SocketAddr) -> Self {
|
|
||||||
let image = Image::from_tga("assets/tilesets/overworld.tga");
|
|
||||||
let tileset = image.to_tileset();
|
|
||||||
let entity_tileset = Image::from_tga("assets/tilesets/entities.tga").to_tileset();
|
|
||||||
let player_pos = (16, 16);
|
|
||||||
Game {
|
|
||||||
tileset,
|
|
||||||
entity_tileset,
|
|
||||||
net: NetClient::new(server_addr),
|
|
||||||
player_entity_id: 0,
|
|
||||||
player_pos,
|
|
||||||
path: VecDeque::new(),
|
|
||||||
route: VecDeque::new(),
|
|
||||||
goal: None,
|
|
||||||
server_tick: 0,
|
|
||||||
tick_age_ms: 0,
|
|
||||||
rtt_ms: 0.0,
|
|
||||||
ping_accum_ms: PING_INTERVAL_MS, // first ping fires immediately
|
|
||||||
last_sched_window: 0,
|
|
||||||
cam: (
|
|
||||||
(player_pos.0 * TILE_PX - VIEW_PX / 2) as f32,
|
|
||||||
(player_pos.1 * TILE_PX - VIEW_PX / 2) as f32,
|
|
||||||
),
|
|
||||||
entities: Vec::new(),
|
|
||||||
lerp: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// True if the world tile at `(wx, wy)` blocks movement. An unknown chunk (outside the
|
|
||||||
/// loaded world) counts as blocked, so we never predict into the void.
|
|
||||||
fn tile_blocked(&self, wx: i32, wy: i32) -> bool {
|
|
||||||
let cid = chunk_id(wx.div_euclid(32) as i16, wy.div_euclid(32) as i16);
|
|
||||||
match self.net.chunk_cache.get(&cid) {
|
|
||||||
Some(c) => {
|
|
||||||
let idx = wy.rem_euclid(32) as usize * 32 + wx.rem_euclid(32) as usize;
|
|
||||||
tile_collidable(c.tiles[idx])
|
|
||||||
}
|
|
||||||
None => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self, render_frame: &mut [u8], dt: usize, input: &InputState)
|
|
||||||
-> Option<GameSignal>
|
|
||||||
{
|
|
||||||
self.tick_age_ms += dt;
|
|
||||||
|
|
||||||
// Periodic RTT sampling — the measurement drives how far ahead steps are
|
|
||||||
// scheduled (`sched_lead`), so it has to stay current on a changing link.
|
|
||||||
self.ping_accum_ms += dt;
|
|
||||||
if self.ping_accum_ms >= PING_INTERVAL_MS {
|
|
||||||
self.ping_accum_ms = 0;
|
|
||||||
self.net.send_ping();
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.mouse_clicked() || input.mouse_held() {
|
|
||||||
self.handle_click(input.mouse_pos(), !input.mouse_clicked());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.step_movement(input);
|
|
||||||
|
|
||||||
if input.button_pressed(GameAction::Confirm) {
|
|
||||||
self.net.send_ping();
|
|
||||||
println!("ping sent");
|
|
||||||
}
|
|
||||||
|
|
||||||
for event in self.net.poll() {
|
|
||||||
match event {
|
|
||||||
NetEvent::Pong { rtt_ms } => {
|
|
||||||
self.rtt_ms = if self.rtt_ms == 0.0 {
|
|
||||||
rtt_ms as f32
|
|
||||||
} else {
|
|
||||||
0.8 * self.rtt_ms + 0.2 * rtt_ms as f32
|
|
||||||
};
|
|
||||||
}
|
|
||||||
NetEvent::State { tick, player_entity_id } => {
|
|
||||||
println!("state tick={tick} player_entity_id={player_entity_id}");
|
|
||||||
self.player_entity_id = player_entity_id;
|
|
||||||
self.server_tick = tick;
|
|
||||||
self.tick_age_ms = 0;
|
|
||||||
}
|
|
||||||
NetEvent::Chunk { chunk_id } => println!("chunk cached id={chunk_id}"),
|
|
||||||
NetEvent::Entity { tick, entities } => {
|
|
||||||
self.player_pos = entities.iter()
|
|
||||||
.find(|e| e.id == self.player_entity_id)
|
|
||||||
.map(|e| (e.pos_x as i32, e.pos_y as i32))
|
|
||||||
.unwrap_or(self.player_pos);
|
|
||||||
self.reconcile_path(tick);
|
|
||||||
self.track_lerp(&entities);
|
|
||||||
self.entities = entities;
|
|
||||||
}
|
|
||||||
NetEvent::Disconnected => println!("disconnected from server"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.button_pressed(GameAction::Cancel) {
|
|
||||||
println!("Goodbye!");
|
|
||||||
return Some(GameSignal::Quit);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.step_camera(dt);
|
|
||||||
self.step_lerp(dt);
|
|
||||||
self.render_viewport(render_frame);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replay one pending delta the way the server will: through the shared movement
|
|
||||||
/// rule. A delta the server is going to reject does not move the prediction either —
|
|
||||||
/// so the prediction can never sit inside a wall, however far position and pending
|
|
||||||
/// steps have diverged.
|
|
||||||
fn replay_step(&self, p: (i32, i32), delta: (i32, i32)) -> (i32, i32) {
|
|
||||||
let to = (p.0 + delta.0, p.1 + delta.1);
|
|
||||||
if step_allowed(p, to, |x, y| self.tile_blocked(x, y)) { to } else { p }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The position movement continues from: the last authoritative position with every
|
|
||||||
/// pending delta replayed on top. Never stored — always derived, so it follows the
|
|
||||||
/// server automatically when a step was rejected or executed elsewhere.
|
|
||||||
fn predicted_pos(&self) -> (i32, i32) {
|
|
||||||
self.path.iter()
|
|
||||||
.fold(self.player_pos, |p, s| self.replay_step(p, s.delta))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Estimated current server tick: the last announced tick advanced by local elapsed
|
|
||||||
/// time. Broadcast latency makes this lag slightly behind the real server clock —
|
|
||||||
/// scheduling one window ahead plus the server's lateness rule absorbs that.
|
|
||||||
fn est_tick(&self) -> f32 {
|
|
||||||
self.server_tick as f32 + self.tick_age_ms as f32 / TICK_MS
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The movement window the estimated server time is currently in.
|
|
||||||
fn est_window(&self) -> u32 {
|
|
||||||
(self.est_tick() / TICKS_PER_MOVE as f32) as u32
|
|
||||||
}
|
|
||||||
|
|
||||||
/// How many windows ahead to schedule so actions arrive *before* their window. The
|
|
||||||
/// tick estimate lags by the downlink and the action spends the uplink in transit —
|
|
||||||
/// together one RTT — so the lead must cover the RTT plus a jitter margin. At least
|
|
||||||
/// one: never schedule into the current window.
|
|
||||||
fn sched_lead(&self) -> u32 {
|
|
||||||
(((self.rtt_ms + LEAD_MARGIN_MS) / WINDOW_MS).ceil() as u32).max(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The newest window whose scheduled step can no longer be retracted in time: a
|
|
||||||
/// cancellation sent now needs the same lead an action does.
|
|
||||||
fn retract_horizon(&self) -> u32 {
|
|
||||||
self.est_window() + self.sched_lead() - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Where the camera wants to be: the viewport centered on the player's tile.
|
|
||||||
fn cam_target(&self) -> (f32, f32) {
|
|
||||||
(
|
|
||||||
(self.player_pos.0 * TILE_PX - VIEW_PX / 2) as f32,
|
|
||||||
(self.player_pos.1 * TILE_PX - VIEW_PX / 2) as f32,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The camera rounded to the pixel grid — the actual top-left of the rendered view.
|
|
||||||
fn cam_px(&self) -> (i32, i32) {
|
|
||||||
(self.cam.0.round() as i32, self.cam.1.round() as i32)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Follow the player linearly at `CAM_SPEED`, axis by axis. Corrections beyond
|
|
||||||
/// `CAM_SNAP_PX` (teleports, respawns) snap outright instead of panning across.
|
|
||||||
fn step_camera(&mut self, dt: usize) {
|
|
||||||
let (tx, ty) = self.cam_target();
|
|
||||||
if (tx - self.cam.0).abs().max((ty - self.cam.1).abs()) > CAM_SNAP_PX {
|
|
||||||
self.cam = (tx, ty);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let step = CAM_SPEED * dt as f32 / 1000.0;
|
|
||||||
let approach = |c: f32, t: f32| {
|
|
||||||
if (t - c).abs() <= step { t } else { c + step * (t - c).signum() }
|
|
||||||
};
|
|
||||||
self.cam = (approach(self.cam.0, tx), approach(self.cam.1, ty));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Click-to-move: translate a framebuffer click into a world tile and adopt it as
|
|
||||||
/// the movement goal. The route is only a client-side plan — executed as ordinary
|
|
||||||
/// tick-addressed actions in `step_movement`, so the server keeps full authority.
|
|
||||||
/// With `hold` (button held after the initial click) this runs every frame and keeps
|
|
||||||
/// steering toward the tile under the cursor, replanning only when that tile changes
|
|
||||||
/// — camera movement alone shifts it too, not just moving the mouse.
|
|
||||||
fn handle_click(&mut self, (mx, my): (i32, i32), hold: bool) {
|
|
||||||
if mx < 0 || my < 0 || mx >= VIEW_PX || my >= VIEW_PX {
|
|
||||||
return; // outside the world viewport
|
|
||||||
}
|
|
||||||
let (cx, cy) = self.cam_px();
|
|
||||||
let goal = ((cx + mx).div_euclid(TILE_PX), (cy + my).div_euclid(TILE_PX));
|
|
||||||
|
|
||||||
// While steering, an unchanged goal needs no replan — unless the route was voided
|
|
||||||
// (blocked step) before reaching it; an empty route with the goal still ahead of
|
|
||||||
// the prediction means exactly that, so plan again.
|
|
||||||
if hold && self.goal == Some(goal)
|
|
||||||
&& (!self.route.is_empty() || self.predicted_pos() == goal)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Plan from where the player will be once the retractable steps are withdrawn:
|
|
||||||
// the confirmed position plus the deltas that can no longer be cancelled in
|
|
||||||
// time — not the tip of a prediction we are about to cancel.
|
|
||||||
let horizon = self.retract_horizon();
|
|
||||||
let start = self.path.iter()
|
|
||||||
.take_while(|s| s.window <= horizon)
|
|
||||||
.fold(self.player_pos, |p, s| self.replay_step(p, s.delta));
|
|
||||||
|
|
||||||
match pathfind::find_path(start, goal, |x, y| self.tile_blocked(x, y)) {
|
|
||||||
Some(steps) => {
|
|
||||||
// The plan changed: retract every scheduled-but-unexecuted step so the
|
|
||||||
// old intent cannot keep playing out on the server.
|
|
||||||
self.retract_future_steps();
|
|
||||||
self.route = steps.into();
|
|
||||||
self.goal = Some(goal);
|
|
||||||
}
|
|
||||||
// A discrete click on an unreachable tile cancels the plan; while steering,
|
|
||||||
// sweeping the cursor across a blocked tile keeps the current plan alive.
|
|
||||||
None if !hold => {
|
|
||||||
self.route.clear();
|
|
||||||
self.goal = None;
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Movement scheduling on the server timeline. A held direction key (which cancels
|
|
||||||
/// any goal) or the next planned route tile is addressed to the *next* movement
|
|
||||||
/// window — at most one step per window, so the send rate follows the server's
|
|
||||||
/// cadence by construction instead of racing it with a local timer. Steps are
|
|
||||||
/// validated locally with the shared movement rule; a blocked route step triggers
|
|
||||||
/// a replan toward the goal.
|
|
||||||
fn step_movement(&mut self, input: &InputState) {
|
|
||||||
// Both axes combine, so two held keys walk diagonally (a king move).
|
|
||||||
let kx = input.button_held(GameAction::Right) as i32
|
|
||||||
- input.button_held(GameAction::Left) as i32;
|
|
||||||
let ky = input.button_held(GameAction::Down) as i32
|
|
||||||
- input.button_held(GameAction::Up) as i32;
|
|
||||||
let dir = (kx != 0 || ky != 0).then_some((kx, ky));
|
|
||||||
|
|
||||||
if dir.is_some() {
|
|
||||||
// Manual input overrides click-to-move entirely, goal included.
|
|
||||||
self.route.clear();
|
|
||||||
self.goal = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let target = self.est_window() + self.sched_lead();
|
|
||||||
let ready = target > self.last_sched_window && self.path.len() < PATH_MAX_LEN;
|
|
||||||
let from = self.predicted_pos();
|
|
||||||
|
|
||||||
// The next step: either from the held key, or the head of the planned route.
|
|
||||||
let step = match dir {
|
|
||||||
Some((dx, dy)) => Some((from.0 + dx, from.1 + dy)),
|
|
||||||
None => self.route.front().copied(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some((nx, ny)) = step
|
|
||||||
&& ready
|
|
||||||
{
|
|
||||||
let allowed = step_allowed(from, (nx, ny), |x, y| self.tile_blocked(x, y));
|
|
||||||
if allowed {
|
|
||||||
if dir.is_none() {
|
|
||||||
self.route.pop_front();
|
|
||||||
}
|
|
||||||
self.path.push_back(PathStep { window: target, delta: (nx - from.0, ny - from.1) });
|
|
||||||
self.net.send_action(
|
|
||||||
delta_action(nx - from.0, ny - from.1),
|
|
||||||
target * TICKS_PER_MOVE,
|
|
||||||
);
|
|
||||||
self.last_sched_window = target;
|
|
||||||
} else if dir.is_none() {
|
|
||||||
self.replan_route();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retract queued steps the server has (very likely) not executed yet: every step
|
|
||||||
/// addressed past the current estimated window gets a NOOP sent to its slot
|
|
||||||
/// (replacement = cancellation) and leaves the prediction. Steps at or before the
|
|
||||||
/// current window are left to be confirmed — cancelling those would race their
|
|
||||||
/// execution. Freed windows become addressable again.
|
|
||||||
fn retract_future_steps(&mut self) {
|
|
||||||
let horizon = self.retract_horizon();
|
|
||||||
while let Some(step) = self.path.back() {
|
|
||||||
if step.window <= horizon {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
self.net.send_action(player_action::NOOP, step.window * TICKS_PER_MOVE);
|
|
||||||
self.path.pop_back();
|
|
||||||
}
|
|
||||||
self.last_sched_window = self.path.back().map_or(horizon, |s| s.window);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replan the route from the current prediction toward the persistent goal — the
|
|
||||||
/// client-side reaction to any surprise (blocked step, server divergence). Gives
|
|
||||||
/// the goal up only when it is reached or has become unreachable.
|
|
||||||
fn replan_route(&mut self) {
|
|
||||||
self.route.clear();
|
|
||||||
let Some(goal) = self.goal else { return };
|
|
||||||
if self.predicted_pos() == goal {
|
|
||||||
self.goal = None;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
match pathfind::find_path(self.predicted_pos(), goal, |x, y| self.tile_blocked(x, y)) {
|
|
||||||
Some(steps) => self.route = steps.into(),
|
|
||||||
None => self.goal = None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fold a fresh authoritative entity list into the interpolation table: a changed
|
|
||||||
/// position starts a new lerp from the old one, a jump of more than one tile
|
|
||||||
/// (Chebyshev — a teleport) snaps, and vanished entities are dropped.
|
|
||||||
fn track_lerp(&mut self, entities: &[EntityInfo]) {
|
|
||||||
for e in entities {
|
|
||||||
let cur = (e.pos_x as i32, e.pos_y as i32);
|
|
||||||
self.lerp.entry(e.id)
|
|
||||||
.and_modify(|l| {
|
|
||||||
if l.cur != cur {
|
|
||||||
let far = (cur.0 - l.cur.0).abs().max((cur.1 - l.cur.1).abs()) > 1;
|
|
||||||
l.prev = if far { cur } else { l.cur };
|
|
||||||
l.cur = cur;
|
|
||||||
l.t_ms = 0;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.or_insert(EntityLerp { prev: cur, cur, t_ms: 0 });
|
|
||||||
}
|
|
||||||
self.lerp.retain(|id, _| entities.iter().any(|e| e.id == *id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Advance all interpolation clocks; each lerp completes after one movement interval.
|
|
||||||
fn step_lerp(&mut self, dt: usize) {
|
|
||||||
for l in self.lerp.values_mut() {
|
|
||||||
l.t_ms = (l.t_ms + dt).min(MOVE_INTERVAL_MS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An entity's render position in world pixels: between its previous and current
|
|
||||||
/// tile, proportional to the time since the current one was confirmed.
|
|
||||||
fn entity_px(&self, e: &EntityInfo) -> (i32, i32) {
|
|
||||||
let cur = (e.pos_x as i32 * TILE_PX, e.pos_y as i32 * TILE_PX);
|
|
||||||
match self.lerp.get(&e.id) {
|
|
||||||
Some(l) => {
|
|
||||||
let f = l.t_ms as f32 / MOVE_INTERVAL_MS as f32;
|
|
||||||
let mix = |a: i32, b: i32| a + ((b - a) as f32 * f).round() as i32;
|
|
||||||
(mix(l.prev.0 * TILE_PX, cur.0), mix(l.prev.1 * TILE_PX, cur.1))
|
|
||||||
}
|
|
||||||
None => cur,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fold a fresh authoritative snapshot into the prediction. The snapshot's tick is
|
|
||||||
/// an acknowledgment cursor on the shared timeline: every movement window at or
|
|
||||||
/// before it has been consumed — executed, rejected or lost, it no longer matters
|
|
||||||
/// which — so those steps expire, and the remaining deltas replay on top of the
|
|
||||||
/// confirmed position (`predicted_pos`). A server surprise thus *shifts* the
|
|
||||||
/// prediction instead of invalidating it; if the shifted route no longer connects
|
|
||||||
/// or misses the goal, send-time validation triggers a replan toward the goal.
|
|
||||||
fn reconcile_path(&mut self, tick: u32) {
|
|
||||||
let ack_window = tick / TICKS_PER_MOVE;
|
|
||||||
while self.path.front().is_some_and(|s| s.window <= ack_window) {
|
|
||||||
self.path.pop_front();
|
|
||||||
}
|
|
||||||
if self.path.is_empty() && self.route.is_empty() && self.goal == Some(self.player_pos) {
|
|
||||||
self.goal = None; // arrived — a later server correction must not walk us back
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_viewport(&self, frame: &mut [u8]) {
|
|
||||||
const WALL: u8 = 0x00;
|
|
||||||
const W: usize = 320;
|
|
||||||
|
|
||||||
frame.fill(0);
|
|
||||||
|
|
||||||
let (cx, cy) = self.cam_px();
|
|
||||||
let tx0 = cx.div_euclid(TILE_PX);
|
|
||||||
let ty0 = cy.div_euclid(TILE_PX);
|
|
||||||
let sx = -cx.rem_euclid(TILE_PX);
|
|
||||||
let sy = -cy.rem_euclid(TILE_PX);
|
|
||||||
|
|
||||||
// One extra row and column: with a sub-tile camera offset the viewport spans
|
|
||||||
// partial tiles on both edges.
|
|
||||||
for vy in 0..=VIEW_TILES {
|
|
||||||
for vx in 0..=VIEW_TILES {
|
|
||||||
let wx = tx0 + vx;
|
|
||||||
let wy = ty0 + vy;
|
|
||||||
let cid = chunk_id(wx.div_euclid(32) as i16, wy.div_euclid(32) as i16);
|
|
||||||
let tile_idx = wy.rem_euclid(32) as usize * 32 + wx.rem_euclid(32) as usize;
|
|
||||||
let tile_id = self.net.chunk_cache.get(&cid)
|
|
||||||
.map(|c| c.tiles[tile_idx])
|
|
||||||
.unwrap_or(0);
|
|
||||||
let px = sx + vx * TILE_PX;
|
|
||||||
let py = sy + vy * TILE_PX;
|
|
||||||
if let Some(tile) = self.tileset.get(tile_id as usize) {
|
|
||||||
pixelhelper::blit_tile(frame, W, px, py, tile);
|
|
||||||
} else {
|
|
||||||
for dy in 0..8 {
|
|
||||||
for dx in 0..8 {
|
|
||||||
pixelhelper::set_pixel(frame, W, px + dx, py + dy, WALL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path pass — in-flight steps bright, planned-route tiles dim; over the world,
|
|
||||||
// under entities.
|
|
||||||
// Path tiles are derived by replaying the pending deltas from the confirmed
|
|
||||||
// position — the same fold the prediction uses (a rejected step marks in place).
|
|
||||||
let mut acc = self.player_pos;
|
|
||||||
let marks = self.path.iter()
|
|
||||||
.map(|s| {
|
|
||||||
acc = self.replay_step(acc, s.delta);
|
|
||||||
(acc, PATH_COLOR)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>().into_iter()
|
|
||||||
.chain(self.route.iter().map(|&p| (p, ROUTE_COLOR)));
|
|
||||||
for ((wx, wy), color) in marks {
|
|
||||||
let px = wx * TILE_PX - cx;
|
|
||||||
let py = wy * TILE_PX - cy;
|
|
||||||
if px <= -TILE_PX || px >= VIEW_PX || py <= -TILE_PX || py >= VIEW_PX { continue; }
|
|
||||||
for dy in 0..8 {
|
|
||||||
for dx in 0..8 {
|
|
||||||
pixelhelper::set_pixel(frame, W, px + dx, py + dy, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entity pass — interpolated between the last two confirmed positions.
|
|
||||||
for e in &self.entities {
|
|
||||||
let (ex, ey) = self.entity_px(e);
|
|
||||||
let px = ex - cx;
|
|
||||||
let py = ey - cy;
|
|
||||||
if px <= -TILE_PX || px >= VIEW_PX || py <= -TILE_PX || py >= VIEW_PX { continue; }
|
|
||||||
if let Some(tile) = self.entity_tileset.get(e.type_id as usize) {
|
|
||||||
pixelhelper::blit_tile(frame, W, px, py, tile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The blit primitives clip against the framebuffer, not the viewport, so partial
|
|
||||||
// tiles on the right edge bleed into the 240..320 strip. Clear it; a real
|
|
||||||
// clip-rect belongs to the UI pass (see roadmap).
|
|
||||||
for y in 0..240usize {
|
|
||||||
frame[y * W + VIEW_PX as usize..y * W + W].fill(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::mem::size_of;
|
|
||||||
use std::net::{SocketAddr, UdpSocket};
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
use bytemuck::bytes_of;
|
|
||||||
use shared::{ActionPacket, ChunkEntry, ChunkPacket, EntityPacket,
|
|
||||||
Header, PingPacket, PongPacket, StatePacket,
|
|
||||||
MAGIC, VERSION, packet_type};
|
|
||||||
|
|
||||||
pub struct CachedChunk {
|
|
||||||
pub version: u16,
|
|
||||||
pub tiles: Box<[u16; 1024]>, // decoded global tile IDs
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EntityInfo {
|
|
||||||
pub id: u32,
|
|
||||||
pub type_id: u16,
|
|
||||||
pub pos_x: i16,
|
|
||||||
pub pos_y: i16,
|
|
||||||
pub hp: u16,
|
|
||||||
pub hp_max: u16,
|
|
||||||
pub flags: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum NetEvent {
|
|
||||||
Pong { rtt_ms: u64 },
|
|
||||||
State { tick: u32, player_entity_id: u32 },
|
|
||||||
Chunk { chunk_id: u32 },
|
|
||||||
/// An authoritative entity snapshot taken at server tick `tick` — the tick doubles
|
|
||||||
/// as the acknowledgment cursor for tick-addressed actions: every movement window
|
|
||||||
/// at or before it has been consumed.
|
|
||||||
Entity { tick: u32, entities: Vec<EntityInfo> },
|
|
||||||
Disconnected,
|
|
||||||
}
|
|
||||||
|
|
||||||
const FNV_BASIS: u32 = 2_166_136_261;
|
|
||||||
const FNV_PRIME: u32 = 16_777_619;
|
|
||||||
|
|
||||||
pub struct NetClient {
|
|
||||||
socket: UdpSocket,
|
|
||||||
ping_sent_at: Option<Instant>,
|
|
||||||
last_action_sent: Instant,
|
|
||||||
last_state_received: Option<Instant>,
|
|
||||||
pub chunk_cache: HashMap<u32, CachedChunk>,
|
|
||||||
last_state_chunks: Option<[ChunkEntry; 9]>,
|
|
||||||
pending_entities: Vec<EntityInfo>,
|
|
||||||
last_entity_checksum: u32,
|
|
||||||
last_state_tick: u32,
|
|
||||||
entity_checksum_accum: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetClient {
|
|
||||||
pub fn new(server_addr: SocketAddr) -> Self {
|
|
||||||
let socket = UdpSocket::bind("0.0.0.0:0").expect("bind failed");
|
|
||||||
socket.connect(server_addr).expect("connect failed");
|
|
||||||
socket.set_nonblocking(true).expect("set_nonblocking failed");
|
|
||||||
Self {
|
|
||||||
socket,
|
|
||||||
ping_sent_at: None,
|
|
||||||
last_action_sent: Instant::now(),
|
|
||||||
last_state_received: None,
|
|
||||||
chunk_cache: HashMap::new(),
|
|
||||||
last_state_chunks: None,
|
|
||||||
pending_entities: Vec::new(),
|
|
||||||
last_entity_checksum: 0,
|
|
||||||
last_state_tick: 0,
|
|
||||||
entity_checksum_accum: FNV_BASIS,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send an action addressed to a server tick (see `ActionPacket::target_tick`).
|
|
||||||
/// `target_tick == 0` is a pure keep-alive / cache-ack with no scheduling intent.
|
|
||||||
pub fn send_action(&mut self, player_action: u16, target_tick: u32) {
|
|
||||||
let packet = ActionPacket {
|
|
||||||
header: Header::new(packet_type::ACTION),
|
|
||||||
auth_token: 0,
|
|
||||||
target_tick,
|
|
||||||
cache: {
|
|
||||||
let mut cache = [ChunkEntry { chunk_id: 0, version: 0 }; 9];
|
|
||||||
if let Some(slots) = self.last_state_chunks {
|
|
||||||
for (i, slot) in slots.iter().enumerate() {
|
|
||||||
let cid = slot.chunk_id;
|
|
||||||
let ver = self.chunk_cache.get(&cid)
|
|
||||||
.map(|c| c.version).unwrap_or(0);
|
|
||||||
cache[i] = ChunkEntry { chunk_id: cid, version: ver };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cache
|
|
||||||
},
|
|
||||||
player_action,
|
|
||||||
};
|
|
||||||
self.socket.send(bytes_of(&packet)).ok();
|
|
||||||
self.last_action_sent = Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_ping(&mut self) {
|
|
||||||
let ping = PingPacket { header: Header::new(packet_type::PING), timestamp_ms: 0 };
|
|
||||||
self.ping_sent_at = Some(Instant::now());
|
|
||||||
self.socket.send(bytes_of(&ping)).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll(&mut self) -> Vec<NetEvent> {
|
|
||||||
const KEEPALIVE: Duration = Duration::from_secs(2);
|
|
||||||
const TIMEOUT: Duration = Duration::from_secs(10);
|
|
||||||
|
|
||||||
if self.last_action_sent.elapsed() >= KEEPALIVE {
|
|
||||||
self.send_action(shared::player_action::NOOP, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut events = Vec::new();
|
|
||||||
let mut buf = [0u8; 1200];
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match self.socket.recv(&mut buf) {
|
|
||||||
Ok(n) => {
|
|
||||||
if n < size_of::<Header>() { continue; }
|
|
||||||
let header: Header = *bytemuck::from_bytes(&buf[..size_of::<Header>()]);
|
|
||||||
if header.magic != MAGIC || header.version != VERSION { continue; }
|
|
||||||
|
|
||||||
match header.packet_type {
|
|
||||||
packet_type::PONG if n >= size_of::<PongPacket>() => {
|
|
||||||
if let Some(sent_at) = self.ping_sent_at.take() {
|
|
||||||
let rtt_ms = sent_at.elapsed().as_millis() as u64;
|
|
||||||
events.push(NetEvent::Pong { rtt_ms });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
packet_type::STATE if n >= size_of::<StatePacket>() => {
|
|
||||||
let pkt: StatePacket = *bytemuck::from_bytes(&buf[..size_of::<StatePacket>()]);
|
|
||||||
self.last_state_received = Some(Instant::now());
|
|
||||||
self.last_state_chunks = Some(pkt.chunks);
|
|
||||||
self.last_entity_checksum = pkt.entity_checksum;
|
|
||||||
self.last_state_tick = pkt.tick;
|
|
||||||
self.pending_entities.clear();
|
|
||||||
self.entity_checksum_accum = FNV_BASIS;
|
|
||||||
events.push(NetEvent::State { tick: pkt.tick, player_entity_id: pkt.player_entity_id });
|
|
||||||
}
|
|
||||||
packet_type::ENTITY if n >= size_of::<EntityPacket>() => {
|
|
||||||
let pkt: EntityPacket = *bytemuck::from_bytes(&buf[..size_of::<EntityPacket>()]);
|
|
||||||
if pkt.tick != self.last_state_tick {
|
|
||||||
// stale datagram from a previous state cycle
|
|
||||||
} else {
|
|
||||||
for i in 0..pkt.entity_count as usize {
|
|
||||||
let e = pkt.entities[i];
|
|
||||||
for byte in bytemuck::bytes_of(&e) {
|
|
||||||
self.entity_checksum_accum ^= *byte as u32;
|
|
||||||
self.entity_checksum_accum =
|
|
||||||
self.entity_checksum_accum.wrapping_mul(FNV_PRIME);
|
|
||||||
}
|
|
||||||
self.pending_entities.push(EntityInfo {
|
|
||||||
id: e.id,
|
|
||||||
type_id: e.type_id,
|
|
||||||
pos_x: e.pos_x,
|
|
||||||
pos_y: e.pos_y,
|
|
||||||
hp: e.hp,
|
|
||||||
hp_max: e.hp_max,
|
|
||||||
flags: e.entity_flags,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if pkt.packet_flags & 1 == 0 {
|
|
||||||
// last datagram in sequence
|
|
||||||
if self.entity_checksum_accum != self.last_entity_checksum {
|
|
||||||
self.send_action(shared::player_action::NOOP, 0);
|
|
||||||
}
|
|
||||||
events.push(NetEvent::Entity {
|
|
||||||
tick: pkt.tick,
|
|
||||||
entities: std::mem::take(&mut self.pending_entities),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
packet_type::CHUNK if n >= size_of::<ChunkPacket>() => {
|
|
||||||
let pkt: ChunkPacket = *bytemuck::from_bytes(&buf[..size_of::<ChunkPacket>()]);
|
|
||||||
let id = pkt.chunk.chunk_id;
|
|
||||||
let version = pkt.chunk.version;
|
|
||||||
let palette = pkt.palette;
|
|
||||||
let raw = pkt.tiles;
|
|
||||||
let mut tiles = Box::new([0u16; 1024]);
|
|
||||||
for i in 0..256usize {
|
|
||||||
let b0 = raw[i * 3];
|
|
||||||
let b1 = raw[i * 3 + 1];
|
|
||||||
let b2 = raw[i * 3 + 2];
|
|
||||||
let ia = (b0 & 0x3F) as usize;
|
|
||||||
let ib = ((b0 >> 6) | ((b1 & 0x0F) << 2)) as usize;
|
|
||||||
let ic = ((b1 >> 4) | ((b2 & 0x03) << 4)) as usize;
|
|
||||||
let id_ = (b2 >> 2) as usize;
|
|
||||||
tiles[i * 4] = palette[ia];
|
|
||||||
tiles[i * 4 + 1] = palette[ib];
|
|
||||||
tiles[i * 4 + 2] = palette[ic];
|
|
||||||
tiles[i * 4 + 3] = palette[id_];
|
|
||||||
}
|
|
||||||
self.chunk_cache.insert(id, CachedChunk { version, tiles });
|
|
||||||
events.push(NetEvent::Chunk { chunk_id: id });
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break,
|
|
||||||
Err(_) => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(t) = self.last_state_received {
|
|
||||||
if t.elapsed() >= TIMEOUT {
|
|
||||||
self.last_state_received = None;
|
|
||||||
events.push(NetEvent::Disconnected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
events
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Executable
+9
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "game"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
sim = { path = "../sim" }
|
||||||
|
pbio = { git = "https://git.bnd.wtf/irrlicht/pbio.git", branch = "truecolor16" }
|
||||||
|
png = "0.18.1"
|
||||||
Executable
+67
@@ -0,0 +1,67 @@
|
|||||||
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
|
||||||
|
/// A decoded image in the framebuffer's pixel format: RGB565 plus a 1-bit alpha
|
||||||
|
/// mask. Truecolor sources are quantized on load (8 bit → 5/6/5 by truncation), so an
|
||||||
|
/// asset can never be in the "wrong" palette — there is none.
|
||||||
|
pub struct Image {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub pixels: Vec<u16>,
|
||||||
|
/// One flag per pixel: `true` = opaque. Alpha ≥ 128 counts as opaque.
|
||||||
|
pub opaque: Vec<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An 8×8 sprite: RGB565 pixels row-major plus an opacity bitmask (bit `y*8+x`).
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Tile {
|
||||||
|
pub px: [u16; 64],
|
||||||
|
pub mask: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Image {
|
||||||
|
/// Load a PNG from disk. Any color type / bit depth the `png` crate can expand to
|
||||||
|
/// 8-bit RGBA is accepted.
|
||||||
|
pub fn from_png(path: &str) -> Self {
|
||||||
|
let file = File::open(path).unwrap_or_else(|e| panic!("failed to open {path}: {e}"));
|
||||||
|
let mut decoder = png::Decoder::new(BufReader::new(file));
|
||||||
|
decoder.set_transformations(png::Transformations::normalize_to_color8() | png::Transformations::ALPHA);
|
||||||
|
let mut reader = decoder.read_info().unwrap_or_else(|e| panic!("bad PNG {path}: {e}"));
|
||||||
|
let mut buf = vec![0u8; reader.output_buffer_size().expect("PNG too large")];
|
||||||
|
let info = reader.next_frame(&mut buf).unwrap_or_else(|e| panic!("bad PNG {path}: {e}"));
|
||||||
|
assert_eq!(info.color_type, png::ColorType::Rgba, "{path}: expected RGBA after expansion");
|
||||||
|
|
||||||
|
let count = (info.width * info.height) as usize;
|
||||||
|
let mut pixels = Vec::with_capacity(count);
|
||||||
|
let mut opaque = Vec::with_capacity(count);
|
||||||
|
for p in buf[..count * 4].chunks_exact(4) {
|
||||||
|
pixels.push(pbio::rgb565(p[0], p[1], p[2]));
|
||||||
|
opaque.push(p[3] >= 128);
|
||||||
|
}
|
||||||
|
Image { width: info.width, height: info.height, pixels, opaque }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split this image into 8×8 tiles, row-major. Tile ID = flat index into the Vec.
|
||||||
|
pub fn to_tileset(&self) -> Vec<Tile> {
|
||||||
|
let w = self.width as usize;
|
||||||
|
let cols = w / 8;
|
||||||
|
let rows = self.height as usize / 8;
|
||||||
|
let mut tiles = Vec::with_capacity(rows * cols);
|
||||||
|
for row in 0..rows {
|
||||||
|
for col in 0..cols {
|
||||||
|
let mut tile = Tile { px: [0; 64], mask: 0 };
|
||||||
|
for tr in 0..8 {
|
||||||
|
for tc in 0..8 {
|
||||||
|
let i = w * (row * 8 + tr) + col * 8 + tc;
|
||||||
|
tile.px[tr * 8 + tc] = self.pixels[i];
|
||||||
|
if self.opaque[i] {
|
||||||
|
tile.mask |= 1 << (tr * 8 + tc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tiles.push(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tiles
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+418
@@ -0,0 +1,418 @@
|
|||||||
|
mod pathfind;
|
||||||
|
mod pixelhelper;
|
||||||
|
|
||||||
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
|
use crate::assets::{Image, Tile};
|
||||||
|
use crate::input::{GameAction, InputState};
|
||||||
|
use sim::entity::Entity;
|
||||||
|
use sim::map::TileMap;
|
||||||
|
use sim::{delta_action, load_world, step_allowed, Sim, TICK_HZ, TICKS_PER_MOVE};
|
||||||
|
|
||||||
|
pub enum GameSignal {
|
||||||
|
Quit,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Length of one base tick. The sim advances in whole ticks; rendering runs at frame
|
||||||
|
/// rate on top and accumulates frame time into ticks.
|
||||||
|
const TICK_MS: f32 = 1000.0 / TICK_HZ as f32;
|
||||||
|
|
||||||
|
/// The movement cadence (movement on every 4th tick → 6 Hz ≈ 167 ms/tile). Entity
|
||||||
|
/// interpolation lerps over this interval.
|
||||||
|
const MOVE_INTERVAL_MS: usize = (TICK_MS * TICKS_PER_MOVE as f32) as usize;
|
||||||
|
|
||||||
|
/// Longest frame the sim will catch up on in one go. Anything slower (debugger, window
|
||||||
|
/// drag) is dropped instead of spiralling into a burst of ticks.
|
||||||
|
const MAX_FRAME_MS: f32 = 250.0;
|
||||||
|
|
||||||
|
/// Color of the planned route: dim blue.
|
||||||
|
const ROUTE_COLOR: u16 = pbio::rgb565(0, 0, 170);
|
||||||
|
|
||||||
|
/// Fill for tiles outside the tileset / world.
|
||||||
|
const VOID_COLOR: u16 = pbio::rgb565(0, 0, 0);
|
||||||
|
|
||||||
|
/// Viewport geometry: 30×30 tiles of 8 px, top-left of the framebuffer.
|
||||||
|
const VIEW_TILES: i32 = 30;
|
||||||
|
const TILE_PX: i32 = 8;
|
||||||
|
const VIEW_PX: i32 = VIEW_TILES * TILE_PX;
|
||||||
|
|
||||||
|
/// Camera pan speed. The player walks 8 px per 167 ms ≈ 48 px/s; the camera is a bit
|
||||||
|
/// faster, so it trails during movement and settles right after the player stops.
|
||||||
|
const CAM_SPEED: f32 = 64.0; // px/s
|
||||||
|
|
||||||
|
/// A position correction farther than this is a teleport — snap instead of panning.
|
||||||
|
const CAM_SNAP_PX: f32 = 96.0;
|
||||||
|
|
||||||
|
/// Render-side smoothing state for one entity: the previous tile and how long ago the
|
||||||
|
/// current one was taken. Positions are tiles; rendering lerps between them in pixels
|
||||||
|
/// over one movement interval.
|
||||||
|
struct EntityLerp {
|
||||||
|
prev: (i32, i32),
|
||||||
|
cur: (i32, i32),
|
||||||
|
t_ms: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Game {
|
||||||
|
tileset: Vec<Tile>,
|
||||||
|
entity_tileset: Vec<Tile>,
|
||||||
|
sim: Sim,
|
||||||
|
player_id: u32,
|
||||||
|
/// Frame time not yet consumed by whole ticks.
|
||||||
|
tick_accum_ms: f32,
|
||||||
|
/// Planned route from click-to-move: tiles still to visit, fed into the sim one
|
||||||
|
/// step per movement window. Keyboard input cancels it.
|
||||||
|
route: VecDeque<(i32, i32)>,
|
||||||
|
/// The persistent movement goal. Outlives the route: a blocked step triggers a
|
||||||
|
/// replan toward it, and while the mouse is held it tracks the tile under the
|
||||||
|
/// cursor. Cleared on arrival, unreachability, keyboard override, or a click on an
|
||||||
|
/// unreachable tile.
|
||||||
|
goal: Option<(i32, i32)>,
|
||||||
|
/// Viewport top-left in world pixels. Follows the player linearly instead of
|
||||||
|
/// snapping tile-to-tile; only rendering rounds it to whole pixels.
|
||||||
|
cam: (f32, f32),
|
||||||
|
/// Per-entity interpolation state, keyed by entity id. Purely cosmetic — all game
|
||||||
|
/// logic keeps using the sim's tile positions.
|
||||||
|
lerp: HashMap<u32, EntityLerp>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Game {
|
||||||
|
pub fn start(map_path: &str) -> Self {
|
||||||
|
let tileset = Image::from_png("assets/tilesets/overworld.png").to_tileset();
|
||||||
|
let entity_tileset = Image::from_png("assets/tilesets/entities.png").to_tileset();
|
||||||
|
|
||||||
|
let t0 = std::time::Instant::now();
|
||||||
|
let map = TileMap::from_csv(map_path);
|
||||||
|
let world = load_world(&map);
|
||||||
|
println!("loaded {map_path}: {}×{} tiles in {:.1} ms",
|
||||||
|
map.width, map.height, t0.elapsed().as_secs_f64() * 1e3);
|
||||||
|
let mut sim = Sim::new(world);
|
||||||
|
let player_id = sim.world.spawn_entity(0, (0, 0), 100);
|
||||||
|
|
||||||
|
let mut game = Game {
|
||||||
|
tileset,
|
||||||
|
entity_tileset,
|
||||||
|
sim,
|
||||||
|
player_id,
|
||||||
|
tick_accum_ms: 0.0,
|
||||||
|
route: VecDeque::new(),
|
||||||
|
goal: None,
|
||||||
|
cam: (0.0, 0.0),
|
||||||
|
lerp: HashMap::new(),
|
||||||
|
};
|
||||||
|
game.cam = game.cam_target();
|
||||||
|
game.track_lerp();
|
||||||
|
game
|
||||||
|
}
|
||||||
|
|
||||||
|
fn player_pos(&self) -> (i32, i32) {
|
||||||
|
let p = self.sim.world.entities[&self.player_id].pos;
|
||||||
|
(p.0 as i32, p.1 as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if the world tile at `(wx, wy)` blocks movement. Outside the loaded world
|
||||||
|
/// counts as blocked — the same rule the sim applies.
|
||||||
|
fn tile_blocked(&self, wx: i32, wy: i32) -> bool {
|
||||||
|
self.sim.world.tile_flags(wx as i16, wy as i16).map_or(true, |f| f.collidable())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, render_frame: &mut [u16], dt: usize, input: &InputState)
|
||||||
|
-> Option<GameSignal>
|
||||||
|
{
|
||||||
|
if input.mouse_clicked() || input.mouse_held() {
|
||||||
|
self.handle_click(input.mouse_pos(), !input.mouse_clicked());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.step_movement(input);
|
||||||
|
|
||||||
|
// Advance the world in whole ticks. After every movement window the route is
|
||||||
|
// synced against the new position and the next step is scheduled right away,
|
||||||
|
// so a slow frame that spans several windows still walks every one of them.
|
||||||
|
self.tick_accum_ms += (dt as f32).min(MAX_FRAME_MS);
|
||||||
|
while self.tick_accum_ms >= TICK_MS {
|
||||||
|
self.tick_accum_ms -= TICK_MS;
|
||||||
|
if self.sim.step() {
|
||||||
|
self.track_lerp();
|
||||||
|
self.sync_route();
|
||||||
|
self.step_movement(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.button_pressed(GameAction::Cancel) {
|
||||||
|
println!("Goodbye!");
|
||||||
|
return Some(GameSignal::Quit);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.step_camera(dt);
|
||||||
|
self.step_lerp(dt);
|
||||||
|
self.render_viewport(render_frame);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where the camera wants to be: the viewport centered on the player's tile.
|
||||||
|
fn cam_target(&self) -> (f32, f32) {
|
||||||
|
let (px, py) = self.player_pos();
|
||||||
|
(
|
||||||
|
(px * TILE_PX - VIEW_PX / 2) as f32,
|
||||||
|
(py * TILE_PX - VIEW_PX / 2) as f32,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The camera rounded to the pixel grid — the actual top-left of the rendered view.
|
||||||
|
fn cam_px(&self) -> (i32, i32) {
|
||||||
|
(self.cam.0.round() as i32, self.cam.1.round() as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Follow the player linearly at `CAM_SPEED`, axis by axis. Corrections beyond
|
||||||
|
/// `CAM_SNAP_PX` (teleports, respawns) snap outright instead of panning across.
|
||||||
|
fn step_camera(&mut self, dt: usize) {
|
||||||
|
let (tx, ty) = self.cam_target();
|
||||||
|
if (tx - self.cam.0).abs().max((ty - self.cam.1).abs()) > CAM_SNAP_PX {
|
||||||
|
self.cam = (tx, ty);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let step = CAM_SPEED * dt as f32 / 1000.0;
|
||||||
|
let approach = |c: f32, t: f32| {
|
||||||
|
if (t - c).abs() <= step { t } else { c + step * (t - c).signum() }
|
||||||
|
};
|
||||||
|
self.cam = (approach(self.cam.0, tx), approach(self.cam.1, ty));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Click-to-move: translate a framebuffer click into a world tile and adopt it as
|
||||||
|
/// the movement goal. With `hold` (button held after the initial click) this runs
|
||||||
|
/// every frame and keeps steering toward the tile under the cursor, replanning only
|
||||||
|
/// when that tile changes — camera movement alone shifts it too, not just moving
|
||||||
|
/// the mouse.
|
||||||
|
fn handle_click(&mut self, (mx, my): (i32, i32), hold: bool) {
|
||||||
|
if mx < 0 || my < 0 || mx >= VIEW_PX || my >= VIEW_PX {
|
||||||
|
return; // outside the world viewport
|
||||||
|
}
|
||||||
|
let (cx, cy) = self.cam_px();
|
||||||
|
let goal = ((cx + mx).div_euclid(TILE_PX), (cy + my).div_euclid(TILE_PX));
|
||||||
|
|
||||||
|
// While steering, an unchanged goal needs no replan — unless the route was voided
|
||||||
|
// (blocked step) before reaching it; an empty route with the goal still ahead
|
||||||
|
// means exactly that, so plan again.
|
||||||
|
if hold && self.goal == Some(goal)
|
||||||
|
&& (!self.route.is_empty() || self.player_pos() == goal)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match pathfind::find_path(self.player_pos(), goal, |x, y| self.tile_blocked(x, y)) {
|
||||||
|
Some(steps) => {
|
||||||
|
self.route = steps.into();
|
||||||
|
self.goal = Some(goal);
|
||||||
|
}
|
||||||
|
// A discrete click on an unreachable tile cancels the plan; while steering,
|
||||||
|
// sweeping the cursor across a blocked tile keeps the current plan alive.
|
||||||
|
None if !hold => {
|
||||||
|
self.route.clear();
|
||||||
|
self.goal = None;
|
||||||
|
self.sim.clear_action(self.player_id);
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Schedule the player's next step: a held direction key (which cancels any goal)
|
||||||
|
/// or the head of the planned route becomes the intent for the next movement
|
||||||
|
/// window. The sim executes at most one intent per window, so holding a key walks
|
||||||
|
/// at the movement cadence by construction. Steps are validated with the shared
|
||||||
|
/// movement rule; a blocked route step triggers a replan toward the goal.
|
||||||
|
fn step_movement(&mut self, input: &InputState) {
|
||||||
|
// Both axes combine, so two held keys walk diagonally (a king move). A tap
|
||||||
|
// shorter than a frame shows up as pressed only.
|
||||||
|
let axis = |neg: GameAction, pos: GameAction| {
|
||||||
|
(input.button_held(pos) || input.button_pressed(pos)) as i32
|
||||||
|
- (input.button_held(neg) || input.button_pressed(neg)) as i32
|
||||||
|
};
|
||||||
|
let kx = axis(GameAction::Left, GameAction::Right);
|
||||||
|
let ky = axis(GameAction::Up, GameAction::Down);
|
||||||
|
let dir = (kx != 0 || ky != 0).then_some((kx, ky));
|
||||||
|
|
||||||
|
if dir.is_some() {
|
||||||
|
// Manual input overrides click-to-move entirely, goal included.
|
||||||
|
self.route.clear();
|
||||||
|
self.goal = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let from = self.player_pos();
|
||||||
|
let step = match dir {
|
||||||
|
Some((dx, dy)) => Some((from.0 + dx, from.1 + dy)),
|
||||||
|
None => self.route.front().copied(),
|
||||||
|
};
|
||||||
|
let Some((nx, ny)) = step else { return };
|
||||||
|
|
||||||
|
if step_allowed(from, (nx, ny), |x, y| self.tile_blocked(x, y)) {
|
||||||
|
self.sim.set_action(self.player_id, delta_action(nx - from.0, ny - from.1));
|
||||||
|
} else if dir.is_none() {
|
||||||
|
self.replan_route();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// After a movement window: drop the route head once the player stands on it, and
|
||||||
|
/// release the goal on arrival.
|
||||||
|
fn sync_route(&mut self) {
|
||||||
|
let pos = self.player_pos();
|
||||||
|
if self.route.front() == Some(&pos) {
|
||||||
|
self.route.pop_front();
|
||||||
|
}
|
||||||
|
if self.route.is_empty() && self.goal == Some(pos) {
|
||||||
|
self.goal = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replan the route from the current position toward the persistent goal — the
|
||||||
|
/// reaction to a blocked step. Gives the goal up only when it is reached or has
|
||||||
|
/// become unreachable.
|
||||||
|
fn replan_route(&mut self) {
|
||||||
|
self.route.clear();
|
||||||
|
let Some(goal) = self.goal else { return };
|
||||||
|
let pos = self.player_pos();
|
||||||
|
if pos == goal {
|
||||||
|
self.goal = None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match pathfind::find_path(pos, goal, |x, y| self.tile_blocked(x, y)) {
|
||||||
|
Some(steps) => self.route = steps.into(),
|
||||||
|
None => self.goal = None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fold the sim's entity positions into the interpolation table: a changed position
|
||||||
|
/// starts a new lerp from the old one, a jump of more than one tile (Chebyshev — a
|
||||||
|
/// teleport) snaps, and vanished entities are dropped.
|
||||||
|
fn track_lerp(&mut self) {
|
||||||
|
let entities = &self.sim.world.entities;
|
||||||
|
for e in entities.values() {
|
||||||
|
let cur = (e.pos.0 as i32, e.pos.1 as i32);
|
||||||
|
self.lerp.entry(e.id)
|
||||||
|
.and_modify(|l| {
|
||||||
|
if l.cur != cur {
|
||||||
|
let far = (cur.0 - l.cur.0).abs().max((cur.1 - l.cur.1).abs()) > 1;
|
||||||
|
l.prev = if far { cur } else { l.cur };
|
||||||
|
l.cur = cur;
|
||||||
|
l.t_ms = 0;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.or_insert(EntityLerp { prev: cur, cur, t_ms: 0 });
|
||||||
|
}
|
||||||
|
self.lerp.retain(|id, _| entities.contains_key(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance all interpolation clocks; each lerp completes after one movement interval.
|
||||||
|
fn step_lerp(&mut self, dt: usize) {
|
||||||
|
for l in self.lerp.values_mut() {
|
||||||
|
l.t_ms = (l.t_ms + dt).min(MOVE_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An entity's render position in world pixels: between its previous and current
|
||||||
|
/// tile, proportional to the time since the current one was taken.
|
||||||
|
fn entity_px(&self, e: &Entity) -> (i32, i32) {
|
||||||
|
let cur = (e.pos.0 as i32 * TILE_PX, e.pos.1 as i32 * TILE_PX);
|
||||||
|
match self.lerp.get(&e.id) {
|
||||||
|
Some(l) => {
|
||||||
|
let f = l.t_ms as f32 / MOVE_INTERVAL_MS as f32;
|
||||||
|
let mix = |a: i32, b: i32| a + ((b - a) as f32 * f).round() as i32;
|
||||||
|
(mix(l.prev.0 * TILE_PX, cur.0), mix(l.prev.1 * TILE_PX, cur.1))
|
||||||
|
}
|
||||||
|
None => cur,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_viewport(&self, frame: &mut [u16]) {
|
||||||
|
const W: usize = 320;
|
||||||
|
|
||||||
|
frame.fill(0);
|
||||||
|
|
||||||
|
let (cx, cy) = self.cam_px();
|
||||||
|
let tx0 = cx.div_euclid(TILE_PX);
|
||||||
|
let ty0 = cy.div_euclid(TILE_PX);
|
||||||
|
let sx = -cx.rem_euclid(TILE_PX);
|
||||||
|
let sy = -cy.rem_euclid(TILE_PX);
|
||||||
|
|
||||||
|
// One extra row and column: with a sub-tile camera offset the viewport spans
|
||||||
|
// partial tiles on both edges.
|
||||||
|
for vy in 0..=VIEW_TILES {
|
||||||
|
for vx in 0..=VIEW_TILES {
|
||||||
|
let wx = tx0 + vx;
|
||||||
|
let wy = ty0 + vy;
|
||||||
|
let (ccx, ccy) = sim::world::tile_to_chunk(wx as i16, wy as i16);
|
||||||
|
let (lx, ly) = sim::world::tile_to_local(wx as i16, wy as i16);
|
||||||
|
let tile_id = self.sim.world.chunk(ccx, ccy)
|
||||||
|
.map(|c| c.get_tile(lx, ly))
|
||||||
|
.unwrap_or(0);
|
||||||
|
let px = sx + vx * TILE_PX;
|
||||||
|
let py = sy + vy * TILE_PX;
|
||||||
|
if let Some(tile) = self.tileset.get(tile_id as usize) {
|
||||||
|
pixelhelper::blit_tile(frame, W, px, py, tile);
|
||||||
|
} else {
|
||||||
|
for dy in 0..8 {
|
||||||
|
for dx in 0..8 {
|
||||||
|
pixelhelper::set_pixel(frame, W, px + dx, py + dy, VOID_COLOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route pass — over the world, under entities.
|
||||||
|
for &(wx, wy) in &self.route {
|
||||||
|
let px = wx * TILE_PX - cx;
|
||||||
|
let py = wy * TILE_PX - cy;
|
||||||
|
if px <= -TILE_PX || px >= VIEW_PX || py <= -TILE_PX || py >= VIEW_PX { continue; }
|
||||||
|
for dy in 0..8 {
|
||||||
|
for dx in 0..8 {
|
||||||
|
pixelhelper::set_pixel(frame, W, px + dx, py + dy, ROUTE_COLOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entity pass — interpolated between the last two tiles.
|
||||||
|
for e in self.sim.world.entities.values() {
|
||||||
|
let (ex, ey) = self.entity_px(e);
|
||||||
|
let px = ex - cx;
|
||||||
|
let py = ey - cy;
|
||||||
|
if px <= -TILE_PX || px >= VIEW_PX || py <= -TILE_PX || py >= VIEW_PX { continue; }
|
||||||
|
if let Some(tile) = self.entity_tileset.get(e.type_id as usize) {
|
||||||
|
pixelhelper::blit_tile(frame, W, px, py, tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The blit primitives clip against the framebuffer, not the viewport, so partial
|
||||||
|
// tiles on the right edge bleed into the 240..320 strip. Clear it; a real
|
||||||
|
// clip-rect belongs to the UI pass (see roadmap).
|
||||||
|
for y in 0..240usize {
|
||||||
|
frame[y * W + VIEW_PX as usize..y * W + W].fill(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod snapshot {
|
||||||
|
/// Renders the first frame headlessly to the PNG named by `$SNAPSHOT` — a way to
|
||||||
|
/// eyeball the renderer without a window. `$SNAPSHOT_MAP` picks the map:
|
||||||
|
/// `SNAPSHOT=/tmp/frame.png cargo test -p game snapshot -- --ignored`
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn dump_frame() {
|
||||||
|
std::env::set_current_dir("..").unwrap();
|
||||||
|
let map = std::env::var("SNAPSHOT_MAP").unwrap_or_else(|_| "assets/map_test".into());
|
||||||
|
let mut g = super::Game::start(&map);
|
||||||
|
let input = crate::input::InputState::new();
|
||||||
|
let mut frame = vec![0u16; 320 * 240];
|
||||||
|
g.update(&mut frame, 0, &input);
|
||||||
|
let out = std::env::var("SNAPSHOT").unwrap();
|
||||||
|
let mut rgb = Vec::with_capacity(320 * 240 * 3);
|
||||||
|
for p in &frame {
|
||||||
|
rgb.push(((p >> 11) as u8) << 3);
|
||||||
|
rgb.push((((p >> 5) & 63) as u8) << 2);
|
||||||
|
rgb.push(((p & 31) as u8) << 3);
|
||||||
|
}
|
||||||
|
let f = std::fs::File::create(out).unwrap();
|
||||||
|
let mut enc = png::Encoder::new(std::io::BufWriter::new(f), 320, 240);
|
||||||
|
enc.set_color(png::ColorType::Rgb);
|
||||||
|
enc.write_header().unwrap().write_image_data(&rgb).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,7 +75,7 @@ pub fn find_path(
|
|||||||
(1, -1), (1, 1), (-1, 1), (-1, -1),
|
(1, -1), (1, 1), (-1, 1), (-1, -1),
|
||||||
] {
|
] {
|
||||||
let next = (cur.0 + dx, cur.1 + dy);
|
let next = (cur.0 + dx, cur.1 + dy);
|
||||||
if !shared::step_allowed(cur, next, &blocked) {
|
if !sim::step_allowed(cur, next, &blocked) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let ng = g + if dx != 0 && dy != 0 { DIAGONAL_COST } else { CARDINAL_COST };
|
let ng = g + if dx != 0 && dy != 0 { DIAGONAL_COST } else { CARDINAL_COST };
|
||||||
@@ -96,7 +96,7 @@ mod tests {
|
|||||||
fn assert_valid(start: (i32, i32), path: &[(i32, i32)], blocked: impl Fn(i32, i32) -> bool) {
|
fn assert_valid(start: (i32, i32), path: &[(i32, i32)], blocked: impl Fn(i32, i32) -> bool) {
|
||||||
let mut from = start;
|
let mut from = start;
|
||||||
for &to in path {
|
for &to in path {
|
||||||
assert!(shared::step_allowed(from, to, &blocked), "illegal step {from:?} → {to:?}");
|
assert!(sim::step_allowed(from, to, &blocked), "illegal step {from:?} → {to:?}");
|
||||||
from = to;
|
from = to;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
/// Write a single palette index. Out-of-bounds coordinates are silently ignored.
|
use crate::assets::Tile;
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn set_pixel(dst: &mut [u8], dst_w: usize, x: i32, y: i32, color: u8) {
|
/// Write a single RGB565 pixel. Out-of-bounds coordinates are silently ignored.
|
||||||
|
pub fn set_pixel(dst: &mut [u16], dst_w: usize, x: i32, y: i32, color: u16) {
|
||||||
let dst_h = (dst.len() / dst_w) as i32;
|
let dst_h = (dst.len() / dst_w) as i32;
|
||||||
if x < 0 || x >= dst_w as i32 || y < 0 || y >= dst_h {
|
if x < 0 || x >= dst_w as i32 || y < 0 || y >= dst_h {
|
||||||
return;
|
return;
|
||||||
@@ -10,8 +11,8 @@ pub fn set_pixel(dst: &mut [u8], dst_w: usize, x: i32, y: i32, color: u8) {
|
|||||||
|
|
||||||
/// Copy a rectangle out of `src` into a new Vec (for building sprites/glyphs).
|
/// Copy a rectangle out of `src` into a new Vec (for building sprites/glyphs).
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn extract(src: &[u8], src_w: usize, x: usize, y: usize, w: usize, h: usize) -> Vec<u8> {
|
pub fn extract(src: &[u16], src_w: usize, x: usize, y: usize, w: usize, h: usize) -> Vec<u16> {
|
||||||
let mut out = vec![0u8; w * h];
|
let mut out = vec![0u16; w * h];
|
||||||
for row in 0..h {
|
for row in 0..h {
|
||||||
let s = src_w * (y + row) + x;
|
let s = src_w * (y + row) + x;
|
||||||
let d = w * row;
|
let d = w * row;
|
||||||
@@ -21,9 +22,10 @@ pub fn extract(src: &[u8], src_w: usize, x: usize, y: usize, w: usize, h: usize)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Blit `src` (width `src_w`, height derived from src.len()) into `dst` at (dx, dy).
|
/// Blit `src` (width `src_w`, height derived from src.len()) into `dst` at (dx, dy).
|
||||||
/// Clips against all four edges of `dst`; the destination position may be negative.
|
/// Opaque copy, no transparency. Clips against all four edges of `dst`; the
|
||||||
|
/// destination position may be negative.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn blit(dst: &mut [u8], dst_w: usize, dx: i32, dy: i32, src: &[u8], src_w: usize) {
|
pub fn blit(dst: &mut [u16], dst_w: usize, dx: i32, dy: i32, src: &[u16], src_w: usize) {
|
||||||
let dst_w_i = dst_w as i32;
|
let dst_w_i = dst_w as i32;
|
||||||
let dst_h = (dst.len() / dst_w) as i32;
|
let dst_h = (dst.len() / dst_w) as i32;
|
||||||
let src_h = (src.len() / src_w) as i32;
|
let src_h = (src.len() / src_w) as i32;
|
||||||
@@ -46,9 +48,9 @@ pub fn blit(dst: &mut [u8], dst_w: usize, dx: i32, dy: i32, src: &[u8], src_w: u
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Blit an 8×8 tile with transparency: index 0 is skipped.
|
/// Blit an 8×8 tile with transparency: pixels whose mask bit is clear are skipped.
|
||||||
/// Clips against all four edges of `dst`; the destination position may be negative.
|
/// Clips against all four edges of `dst`; the destination position may be negative.
|
||||||
pub fn blit_tile(dst: &mut [u8], dst_w: usize, dx: i32, dy: i32, tile: &[u8; 64]) {
|
pub fn blit_tile(dst: &mut [u16], dst_w: usize, dx: i32, dy: i32, tile: &Tile) {
|
||||||
let dst_w_i = dst_w as i32;
|
let dst_w_i = dst_w as i32;
|
||||||
let dst_h = (dst.len() / dst_w) as i32;
|
let dst_h = (dst.len() / dst_w) as i32;
|
||||||
|
|
||||||
@@ -62,9 +64,9 @@ pub fn blit_tile(dst: &mut [u8], dst_w: usize, dx: i32, dy: i32, tile: &[u8; 64]
|
|||||||
if x < 0 || x >= dst_w_i {
|
if x < 0 || x >= dst_w_i {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let px = tile[(row * 8 + col) as usize];
|
let i = (row * 8 + col) as usize;
|
||||||
if px != 0 {
|
if tile.mask & (1 << i) != 0 {
|
||||||
dst[dst_w * y as usize + x as usize] = px;
|
dst[dst_w * y as usize + x as usize] = tile.px[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,15 +4,13 @@ use std::time::{Duration, Instant};
|
|||||||
mod assets;
|
mod assets;
|
||||||
mod game;
|
mod game;
|
||||||
mod input;
|
mod input;
|
||||||
mod net;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut plat = Platform::new(PlatformConfig {
|
let mut plat = Platform::new(PlatformConfig {
|
||||||
title: "Roguelike".into(),
|
title: "Forgotten Caves".into(),
|
||||||
window_size: (800, 600),
|
window_size: (800, 600),
|
||||||
framebuffer_size: (320, 240),
|
framebuffer_size: (320, 240),
|
||||||
aspect_ratio: Some(4.0 / 3.0),
|
aspect_ratio: Some(4.0 / 3.0),
|
||||||
palette: pbio::palette::RGB332,
|
|
||||||
vsync: true,
|
vsync: true,
|
||||||
mouse_capture: false,
|
mouse_capture: false,
|
||||||
mouse_visible: true,
|
mouse_visible: true,
|
||||||
@@ -21,12 +19,9 @@ fn main() {
|
|||||||
let input_map = input::InputMap::new();
|
let input_map = input::InputMap::new();
|
||||||
let mut input_state = input::InputState::new();
|
let mut input_state = input::InputState::new();
|
||||||
let mut last_update = Instant::now();
|
let mut last_update = Instant::now();
|
||||||
// Optional server address (e.g. netsim proxy): `client 127.0.0.1:7778`.
|
// Optional map path: `game assets/map_large`.
|
||||||
let server_addr = std::env::args().nth(1)
|
let map_path = std::env::args().nth(1).unwrap_or_else(|| "assets/map_test".into());
|
||||||
.unwrap_or_else(|| "127.0.0.1:7777".into())
|
let mut game = game::Game::start(&map_path);
|
||||||
.parse()
|
|
||||||
.expect("bad server address");
|
|
||||||
let mut game = game::Game::start(server_addr);
|
|
||||||
|
|
||||||
while !plat.should_close() {
|
while !plat.should_close() {
|
||||||
plat.poll_events(Some(Duration::from_millis(20)));
|
plat.poll_events(Some(Duration::from_millis(20)));
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
//! Bad-internet simulator: a UDP proxy between game client and server that adds
|
|
||||||
//! delay, jitter and packet loss, per direction. Dev tool only — the game itself
|
|
||||||
//! never knows it exists.
|
|
||||||
//!
|
|
||||||
//! Usage:
|
|
||||||
//! cargo run -p netsim -- [--listen 7778] [--upstream 127.0.0.1:7777]
|
|
||||||
//! [--delay MS] [--jitter MS] [--loss PERCENT]
|
|
||||||
//! [--up-delay MS] [--up-jitter MS] [--up-loss PERCENT]
|
|
||||||
//! [--down-delay MS] [--down-jitter MS] [--down-loss PERCENT]
|
|
||||||
//!
|
|
||||||
//! `--delay/--jitter/--loss` set both directions; the `--up-*` (client → server) and
|
|
||||||
//! `--down-*` (server → client) variants override one direction. Jitter is uniform in
|
|
||||||
//! ±MS around the delay; reordering emerges from jitter naturally, as on a real link.
|
|
||||||
//! Point the client at the listen port (e.g. `client 127.0.0.1:7778`).
|
|
||||||
|
|
||||||
use std::cmp::Reverse;
|
|
||||||
use std::collections::BinaryHeap;
|
|
||||||
use std::net::{SocketAddr, UdpSocket};
|
|
||||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct LinkParams {
|
|
||||||
delay_ms: f32,
|
|
||||||
jitter_ms: f32,
|
|
||||||
loss_pct: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Xorshift64* — plenty for impairment dice; avoids pulling in a rand dependency.
|
|
||||||
struct Rng(u64);
|
|
||||||
|
|
||||||
impl Rng {
|
|
||||||
fn new() -> Self {
|
|
||||||
let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64;
|
|
||||||
Rng(seed | 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next(&mut self) -> u64 {
|
|
||||||
let mut x = self.0;
|
|
||||||
x ^= x >> 12;
|
|
||||||
x ^= x << 25;
|
|
||||||
x ^= x >> 27;
|
|
||||||
self.0 = x;
|
|
||||||
x.wrapping_mul(0x2545_F491_4F6C_DD1D)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Uniform in [0, 1).
|
|
||||||
fn unit(&mut self) -> f32 {
|
|
||||||
(self.next() >> 40) as f32 / (1u64 << 24) as f32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LinkParams {
|
|
||||||
/// Roll the dice for one datagram: `None` = lost, `Some(d)` = deliver after `d`.
|
|
||||||
fn impair(&self, rng: &mut Rng) -> Option<Duration> {
|
|
||||||
if rng.unit() * 100.0 < self.loss_pct {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let jitter = (rng.unit() * 2.0 - 1.0) * self.jitter_ms;
|
|
||||||
Some(Duration::from_secs_f32((self.delay_ms + jitter).max(0.0) / 1000.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A datagram waiting for its delivery time. `Up` = client → server.
|
|
||||||
enum Dir {
|
|
||||||
Up,
|
|
||||||
Down,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Pending {
|
|
||||||
due: Instant,
|
|
||||||
dir: Dir,
|
|
||||||
data: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// BinaryHeap ordering: only `due` matters (earliest first via Reverse).
|
|
||||||
impl PartialEq for Pending {
|
|
||||||
fn eq(&self, other: &Self) -> bool { self.due == other.due }
|
|
||||||
}
|
|
||||||
impl Eq for Pending {}
|
|
||||||
impl PartialOrd for Pending {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) }
|
|
||||||
}
|
|
||||||
impl Ord for Pending {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.due.cmp(&other.due) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_args() -> (u16, SocketAddr, LinkParams, LinkParams) {
|
|
||||||
let mut listen: u16 = 7778;
|
|
||||||
let mut upstream: SocketAddr = "127.0.0.1:7777".parse().unwrap();
|
|
||||||
let mut both = LinkParams { delay_ms: 0.0, jitter_ms: 0.0, loss_pct: 0.0 };
|
|
||||||
let mut up_over = [None::<f32>; 3]; // delay, jitter, loss
|
|
||||||
let mut down_over = [None::<f32>; 3];
|
|
||||||
|
|
||||||
let mut args = std::env::args().skip(1);
|
|
||||||
while let Some(flag) = args.next() {
|
|
||||||
let val = args.next().unwrap_or_else(|| panic!("missing value for {flag}"));
|
|
||||||
let num = || val.parse::<f32>().unwrap_or_else(|_| panic!("bad number for {flag}: {val}"));
|
|
||||||
match flag.as_str() {
|
|
||||||
"--listen" => listen = val.parse().expect("bad listen port"),
|
|
||||||
"--upstream" => upstream = val.parse().expect("bad upstream address"),
|
|
||||||
"--delay" => both.delay_ms = num(),
|
|
||||||
"--jitter" => both.jitter_ms = num(),
|
|
||||||
"--loss" => both.loss_pct = num(),
|
|
||||||
"--up-delay" => up_over[0] = Some(num()),
|
|
||||||
"--up-jitter" => up_over[1] = Some(num()),
|
|
||||||
"--up-loss" => up_over[2] = Some(num()),
|
|
||||||
"--down-delay" => down_over[0] = Some(num()),
|
|
||||||
"--down-jitter" => down_over[1] = Some(num()),
|
|
||||||
"--down-loss" => down_over[2] = Some(num()),
|
|
||||||
_ => panic!("unknown flag {flag}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let apply = |over: [Option<f32>; 3]| LinkParams {
|
|
||||||
delay_ms: over[0].unwrap_or(both.delay_ms),
|
|
||||||
jitter_ms: over[1].unwrap_or(both.jitter_ms),
|
|
||||||
loss_pct: over[2].unwrap_or(both.loss_pct),
|
|
||||||
};
|
|
||||||
(listen, upstream, apply(up_over), apply(down_over))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let (listen, upstream, up, down) = parse_args();
|
|
||||||
|
|
||||||
let client_sock = UdpSocket::bind(("127.0.0.1", listen)).expect("bind listen port");
|
|
||||||
let server_sock = UdpSocket::bind("127.0.0.1:0").expect("bind upstream socket");
|
|
||||||
server_sock.connect(upstream).expect("connect upstream");
|
|
||||||
client_sock.set_nonblocking(true).unwrap();
|
|
||||||
server_sock.set_nonblocking(true).unwrap();
|
|
||||||
|
|
||||||
println!("netsim: 127.0.0.1:{listen} ⇄ {upstream}");
|
|
||||||
println!(" up: {:.0} ms ±{:.0} ms, {:.1}% loss", up.delay_ms, up.jitter_ms, up.loss_pct);
|
|
||||||
println!(" down: {:.0} ms ±{:.0} ms, {:.1}% loss", down.delay_ms, down.jitter_ms, down.loss_pct);
|
|
||||||
|
|
||||||
let mut rng = Rng::new();
|
|
||||||
let mut queue: BinaryHeap<Reverse<Pending>> = BinaryHeap::new();
|
|
||||||
// The one game client, learned from its first datagram (re-learned on address change).
|
|
||||||
let mut client_addr: Option<SocketAddr> = None;
|
|
||||||
let mut buf = [0u8; 2048];
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let now = Instant::now();
|
|
||||||
|
|
||||||
while let Ok((n, from)) = client_sock.recv_from(&mut buf) {
|
|
||||||
client_addr = Some(from);
|
|
||||||
if let Some(d) = up.impair(&mut rng) {
|
|
||||||
queue.push(Reverse(Pending { due: now + d, dir: Dir::Up, data: buf[..n].to_vec() }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while let Ok(n) = server_sock.recv(&mut buf) {
|
|
||||||
if let Some(d) = down.impair(&mut rng) {
|
|
||||||
queue.push(Reverse(Pending { due: now + d, dir: Dir::Down, data: buf[..n].to_vec() }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while queue.peek().is_some_and(|Reverse(p)| p.due <= now) {
|
|
||||||
let Reverse(p) = queue.pop().unwrap();
|
|
||||||
match p.dir {
|
|
||||||
Dir::Up => { server_sock.send(&p.data).ok(); }
|
|
||||||
Dir::Down => {
|
|
||||||
if let Some(addr) = client_addr {
|
|
||||||
client_sock.send_to(&p.data, addr).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::thread::sleep(Duration::from_millis(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Current state
|
## Current state
|
||||||
|
|
||||||
Input is fully decoupled from the windowing backend. `client/src/input.rs` owns all input
|
Input is fully decoupled from the windowing backend. `game/src/input.rs` owns all input
|
||||||
types. `main.rs` translates `pbio::Event::Key` events into `GameAction` values each frame and
|
types. `main.rs` translates `pbio::Event::Key` events into `GameAction` values each frame and
|
||||||
stores them in an `InputState`. `game::update()` receives a `&InputState` and queries it with
|
stores them in an `InputState`. `game::update()` receives a `&InputState` and queries it with
|
||||||
three distinct semantics. (The platform/windowing layer lives in the external `pbio` crate;
|
three distinct semantics. (The platform/windowing layer lives in the external `pbio` crate;
|
||||||
@@ -10,7 +10,7 @@ game code only ever sees `pbio::Key`, never a raw backend type.)
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### `GameAction` — `client/src/input.rs`
|
### `GameAction` — `game/src/input.rs`
|
||||||
|
|
||||||
Logical actions the game cares about. Game code never sees backend key types.
|
Logical actions the game cares about. Game code never sees backend key types.
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ Logical actions the game cares about. Game code never sees backend key types.
|
|||||||
pub enum GameAction { Up, Down, Left, Right, Confirm, Cancel }
|
pub enum GameAction { Up, Down, Left, Right, Confirm, Cancel }
|
||||||
```
|
```
|
||||||
|
|
||||||
### `InputMap` — `client/src/input.rs`
|
### `InputMap` — `game/src/input.rs`
|
||||||
|
|
||||||
Translates a `pbio::Key` to an `Option<GameAction>`. All key bindings live here.
|
Translates a `pbio::Key` to an `Option<GameAction>`. All key bindings live here.
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ Translates a `pbio::Key` to an `Option<GameAction>`. All key bindings live here.
|
|||||||
| Enter | Confirm |
|
| Enter | Confirm |
|
||||||
| Escape | Cancel |
|
| Escape | Cancel |
|
||||||
|
|
||||||
### `InputState` — `client/src/input.rs`
|
### `InputState` — `game/src/input.rs`
|
||||||
|
|
||||||
Three internal buffers, populated from `pbio::Event::Key` events in `main.rs`:
|
Three internal buffers, populated from `pbio::Event::Key` events in `main.rs`:
|
||||||
|
|
||||||
|
|||||||
+19
-12
@@ -2,15 +2,22 @@
|
|||||||
|
|
||||||
## Current state
|
## Current state
|
||||||
|
|
||||||
TGA loading is implemented in `client/src/assets.rs`. `Image::from_tga(path)` reads the file,
|
PNG loading is implemented in `game/src/assets.rs`. `Image::from_png(path)` decodes via the
|
||||||
parses the 18-byte header, skips the colormap block, and returns an `Image` — supporting
|
`png` crate (any color type, expanded to 8-bit RGBA) and quantizes on load to the
|
||||||
type 1 (uncompressed) and type 9 (RLE) via the private `decode_rle()` function in the same
|
framebuffer's RGB565 format with `pbio::rgb565`; alpha ≥ 128 counts as opaque. The result
|
||||||
module. `game.rs` uses it directly: `Image::from_tga("assets/tilesets/overworld.tga")`.
|
is an `Image { width, height, pixels: Vec<u16>, opaque: Vec<bool> }`. Artists work in
|
||||||
|
truecolor; there is no palette an asset could be "in the wrong order" for. `game.rs` uses it
|
||||||
|
directly: `Image::from_png("assets/tilesets/overworld.png")`.
|
||||||
|
|
||||||
`Image` also provides `to_tileset() -> Vec<[u8; 64]>`, which splits the image into 8×8
|
`Image` also provides `to_tileset() -> Vec<Tile>`, which splits the image into 8×8 tiles
|
||||||
tiles row-major. Every tile is emitted; tile ID is the flat row-major index into the Vec,
|
row-major. A `Tile` is 64 RGB565 pixels plus a `u64` opacity bitmask. Every tile is
|
||||||
so tiles are never skipped or reordered. (Transparency is a per-pixel concern handled at
|
emitted; tile ID is the flat row-major index into the Vec, so tiles are never skipped or
|
||||||
blit time by `blit_tile`, not by the splitter.)
|
reordered. `overworld.png` is the unmodified Mini-Medieval `Overworld.png` sheet (24
|
||||||
|
columns), so Tiled tile ids line up with the Vec index.
|
||||||
|
|
||||||
|
History: the first iteration used 8-bit indexed TGAs on a palette framebuffer (RGB332,
|
||||||
|
`pbio` master branch). The colormap-ordering fragility and the index-0-is-transparent
|
||||||
|
convention are gone with the switch to `pbio`'s `truecolor16` branch.
|
||||||
|
|
||||||
`AssetStore` (a named registry) is **not yet implemented**. Assets are currently loaded
|
`AssetStore` (a named registry) is **not yet implemented**. Assets are currently loaded
|
||||||
inline in `Game::start()`.
|
inline in `Game::start()`.
|
||||||
@@ -22,12 +29,12 @@ files directly at the call site.
|
|||||||
|
|
||||||
## Design notes
|
## Design notes
|
||||||
|
|
||||||
- `Image` struct: `width: u32`, `height: u32`, `pixels: Vec<u8>` — done.
|
- `Image` struct: `width: u32`, `height: u32`, `pixels: Vec<u16>`, `opaque: Vec<bool>` — done.
|
||||||
- A simple `AssetStore` could be a `HashMap<&'static str, Image>` loaded at startup.
|
- A simple `AssetStore` could be a `HashMap<&'static str, Image>` loaded at startup.
|
||||||
- No streaming needed — the whole game is small enough to load everything upfront in `start()`.
|
- No streaming needed — the whole game is small enough to load everything upfront in `start()`.
|
||||||
- Palette index 0 is transparent when blitting sprites. This convention is enforced by
|
- Transparency is the tile's mask bit, checked by `blit_tile()` in `pixelhelper.rs`. Any
|
||||||
`blit_tile()` in `pixelhelper.rs`; the loader itself does not need to handle it.
|
color — black included — can be an opaque pixel.
|
||||||
- Pixel assets are stored as flat `Vec<u8>` of palette indices, row-major, width × height bytes.
|
- Pixel assets are stored as flat `Vec<u16>` of RGB565 values, row-major, width × height.
|
||||||
- Future: consider embedding assets with `include_bytes!` to produce a single binary.
|
- Future: consider embedding assets with `include_bytes!` to produce a single binary.
|
||||||
|
|
||||||
## Open questions
|
## Open questions
|
||||||
|
|||||||
@@ -6,20 +6,19 @@ No text rendering exists yet. No font loading or glyph blitting code exists.
|
|||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
|
|
||||||
Draw ASCII text into the `u8` framebuffer using a bitmap font, respecting the palette-index
|
Draw ASCII text into the RGB565 framebuffer using a bitmap font.
|
||||||
color model (one u8 per pixel, no RGBA).
|
|
||||||
|
|
||||||
## Design notes
|
## Design notes
|
||||||
|
|
||||||
- A bitmap font is the right fit: each glyph is a small tile of palette indices, blitted
|
- A bitmap font is the right fit: each glyph is a small masked tile, blitted
|
||||||
directly into the framebuffer using the existing blit primitive in `pixelhelper.rs`.
|
directly into the framebuffer using the existing blit primitive in `pixelhelper.rs`.
|
||||||
- Likely approach: a fixed 8×8 or similar glyph grid packed into a TGA or raw binary,
|
- Likely approach: a fixed 8×8 or similar glyph grid packed into a PNG,
|
||||||
one glyph per ASCII codepoint starting at 0x20 (space).
|
one glyph per ASCII codepoint starting at 0x20 (space).
|
||||||
- The font image itself should be indexed: foreground pixels carry a nonzero palette index
|
- The font image only needs its alpha mask: glyph pixels are opaque, background transparent;
|
||||||
(caller supplies the color), background pixels are index 0 (transparent/skip).
|
the caller supplies the color and `draw_text` paints the mask with it.
|
||||||
- API sketch:
|
- API sketch:
|
||||||
```rust
|
```rust
|
||||||
fn draw_text(frame: &mut [u8], font: &Font, x: i32, y: i32, color: u8, text: &str);
|
fn draw_text(frame: &mut [u16], font: &Font, x: i32, y: i32, color: u16, text: &str);
|
||||||
```
|
```
|
||||||
- `Font` holds the glyph sheet as an `Image` plus glyph width/height.
|
- `Font` holds the glyph sheet as an `Image` plus glyph width/height.
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,41 @@
|
|||||||
# Hybrid Tick Loop
|
# Hybrid Tick Loop
|
||||||
|
|
||||||
Single 24 Hz base tick; subsystems self-select frequency via stride scheduling. Replaces the old
|
Single 24 Hz base tick; subsystems self-select frequency via stride scheduling. The sim is
|
||||||
3-tier (20/10/1 Hz buckets) description from roadmap item 06.
|
headless (`sim` crate) and advanced tick by tick from the game loop.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Driving the sim from the render loop
|
||||||
|
|
||||||
|
Rendering runs at frame rate; the sim runs in whole ticks. `Game::update` accumulates frame
|
||||||
|
time and calls `Sim::step()` once per elapsed `TICK_MS` (`game/src/game.rs`):
|
||||||
|
|
||||||
|
```rust
|
||||||
|
self.tick_accum_ms += (dt as f32).min(MAX_FRAME_MS);
|
||||||
|
while self.tick_accum_ms >= TICK_MS {
|
||||||
|
self.tick_accum_ms -= TICK_MS;
|
||||||
|
if self.sim.step() { // true when a movement window executed
|
||||||
|
self.track_lerp(); // entity positions may have changed
|
||||||
|
self.sync_route(); // click-to-move bookkeeping
|
||||||
|
self.step_movement(input);// schedule the next step right away
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`MAX_FRAME_MS` (250 ms) caps how much a single slow frame catches up, so a debugger pause or
|
||||||
|
window drag drops time instead of spiralling into a burst of ticks. Rendering interpolates
|
||||||
|
entity positions between tiles over one movement interval (`EntityLerp`), so the 6 Hz
|
||||||
|
movement cadence looks smooth at any frame rate.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Stride scheduling
|
## Stride scheduling
|
||||||
|
|
||||||
All subsystems hang off one monotonic `tick: u32` counter. Each subsystem fires when its
|
All subsystems hang off one monotonic `Sim::tick` counter. Each subsystem fires when its
|
||||||
stride condition is met. **Current implementation:**
|
stride condition is met. **Current implementation:**
|
||||||
|
|
||||||
```
|
```
|
||||||
tick % 1 == 0 → input processing, inbound recv (main.rs)
|
tick % 1 == 0 → (game loop) input processing, intent scheduling
|
||||||
tick % 2 == send_phase → broadcast state to clients, phase-staggered (net.rs, 12 Hz/client)
|
|
||||||
tick % 4 == 0 → entity_tick() — movement / collision (sim.rs, 6 Hz)
|
tick % 4 == 0 → entity_tick() — movement / collision (sim.rs, 6 Hz)
|
||||||
tick % 8 == 0 → (reserved) slower AI routines, pathfinding refresh
|
tick % 8 == 0 → (reserved) slower AI routines, pathfinding refresh
|
||||||
tick % 192 == 0 → (reserved) world simulation (8-sec cycle: weather, daylight, respawns)
|
tick % 192 == 0 → (reserved) world simulation (8-sec cycle: weather, daylight, respawns)
|
||||||
@@ -26,8 +49,21 @@ at `% 192` is exactly an 8-second cycle.
|
|||||||
`entity_tick` = one tile of movement, so the tick rate *is* the movement rate. 6 Hz yields the
|
`entity_tick` = one tile of movement, so the tick rate *is* the movement rate. 6 Hz yields the
|
||||||
target ~6 tiles/sec. Once `energy`/`speed` are reintroduced, `entity_tick` can move up to 12 Hz
|
target ~6 tiles/sec. Once `energy`/`speed` are reintroduced, `entity_tick` can move up to 12 Hz
|
||||||
(`% 2`) and per-entity `speed` sets the effective movement rate instead — decoupling sim rate
|
(`% 2`) and per-entity `speed` sets the effective movement rate instead — decoupling sim rate
|
||||||
from movement rate. The broadcast rate (12 Hz/client) is deliberately higher than the movement
|
from movement rate.
|
||||||
rate: it keeps position latency low and adds redundancy against packet loss.
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Intents: one action per entity per movement window
|
||||||
|
|
||||||
|
Actors do not act directly on the world; they schedule an intent (`Sim::set_action`). The
|
||||||
|
sim keeps exactly one intent per entity, setting it again replaces it, and executing the
|
||||||
|
movement window consumes it. So an actor moves at most one tile per window no matter how
|
||||||
|
often it changes its mind in between, and a tap shorter than a window still registers.
|
||||||
|
|
||||||
|
The player's intent is (re)set every frame from held keys or the click-to-move route; NPCs
|
||||||
|
will use the same call from their `think()` (roadmap 08). Validation goes through
|
||||||
|
`sim::step_allowed` on both sides — the pathfinder plans with the exact function the sim
|
||||||
|
executes, so a planned route never contains a step the sim will reject.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -57,61 +93,3 @@ if entity.energy >= ACTION_COST {
|
|||||||
Fast entities (`speed >= ACTION_COST`) act every tick. Slow entities act every N ticks
|
Fast entities (`speed >= ACTION_COST`) act every tick. Slow entities act every N ticks
|
||||||
naturally without any scheduler involvement. Haste and slow effects become simple `speed`
|
naturally without any scheduler involvement. Haste and slow effects become simple `speed`
|
||||||
modifiers — no special-case scheduling needed.
|
modifiers — no special-case scheduling needed.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Simulation rate vs network rate
|
|
||||||
|
|
||||||
Sim and net rates are independent. The current targets:
|
|
||||||
|
|
||||||
| Layer | Rate | Stride |
|
|
||||||
|-------|------|--------|
|
|
||||||
| Base loop | 24 Hz | every tick |
|
|
||||||
| Network broadcast (per client, phase-staggered) | 12 Hz | `tick % 2 == send_phase` |
|
|
||||||
| Entity tick (movement / collision) | 6 Hz | `tick % 4 == 0` |
|
|
||||||
|
|
||||||
Clients receive a fresh `EntityPacket` every ~83 ms. For tile-based movement at 5–6 tiles/sec
|
|
||||||
this is sufficient with comfortable headroom.
|
|
||||||
|
|
||||||
Rough bandwidth per client at 12 Hz:
|
|
||||||
- `StatePacket` 72 B × 12 = 864 B/s
|
|
||||||
- `EntityPacket` ~1200 B × 12 = 14 400 B/s
|
|
||||||
- Total: ~15 KB/s outbound per client
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tick-offset broadcasting (phase staggering)
|
|
||||||
|
|
||||||
Rather than flushing all clients on the same tick, assign each client a `send_phase` at
|
|
||||||
connection time and send only when the client's phase matches:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// ClientState gains:
|
|
||||||
send_phase: u8, // assigned as entity_id % BROADCAST_STRIDE at creation
|
|
||||||
|
|
||||||
// broadcast() skips clients whose phase doesn't match current tick:
|
|
||||||
if tick % BROADCAST_STRIDE as u32 != cs.send_phase as u32 { continue; }
|
|
||||||
```
|
|
||||||
|
|
||||||
Benefits:
|
|
||||||
- Outbound NIC load is flat across ticks instead of spiking every 2nd tick
|
|
||||||
- Scales with player count without architectural changes
|
|
||||||
- Each client still receives state at the same effective rate
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Why higher tick rate doesn't help high-ping players
|
|
||||||
|
|
||||||
```
|
|
||||||
Perceived latency ≈ RTT + tick_processing_delay + state_interval
|
|
||||||
|
|
||||||
LTE @ 100ms RTT, 24Hz sim, 12Hz net:
|
|
||||||
worst case: 100 + 42 + 83 = 225ms ← RTT-dominated
|
|
||||||
|
|
||||||
Same at 64Hz sim, 20Hz net:
|
|
||||||
worst case: 100 + 15 + 50 = 165ms ← 60ms gain, negligible for roguelike
|
|
||||||
```
|
|
||||||
|
|
||||||
RTT is the dominant term. Higher tick rates yield diminishing returns and increase server
|
|
||||||
CPU load for minimal perceived benefit. 24 Hz is sufficient for deliberate tile-based input
|
|
||||||
and remains acceptable at LTE latencies (~100ms RTT) and remote locations (~200ms RTT).
|
|
||||||
|
|||||||
+14
-10
@@ -1,7 +1,7 @@
|
|||||||
# Map Loader
|
# Map Loader
|
||||||
|
|
||||||
Loads level data from a Tiled CSV export, replacing the old hardcoded `build_world` wall loop.
|
Loads level data from a Tiled CSV export, replacing the old hardcoded `build_world` wall loop.
|
||||||
Implemented in `server/src/map.rs`; wired into the world in `server/src/main.rs::load_world`.
|
Implemented in `sim/src/map.rs`; wired into the world in `sim/src/lib.rs::load_world`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -37,8 +37,8 @@ therefore shows up in the CSV as a large *negative* decimal when read as `i32`.
|
|||||||
|
|
||||||
The loader parses each field as `i64`, reinterprets the low 32 bits as `u32`, and strips the
|
The loader parses each field as `i64`, reinterprets the low 32 bits as `u32`, and strips the
|
||||||
flip flags with `& 0x1FFF_FFFF`, leaving the bare tile id. **Flip orientation is discarded for
|
flip flags with `& 0x1FFF_FFFF`, leaving the bare tile id. **Flip orientation is discarded for
|
||||||
now** — flipped tiles render unflipped. Real flipping would need a flip-aware `blit_tile` on
|
now** — flipped tiles render unflipped. Real flipping would need a flip-aware `blit_tile` plus
|
||||||
the client plus the flags carried through the protocol; deferred to the camera/sprite pass (09).
|
a flip flag next to the tile id in the chunk; deferred to the camera/sprite pass (09).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -46,19 +46,23 @@ the client plus the flags carried through the protocol; deferred to the camera/s
|
|||||||
|
|
||||||
Tile ids are used directly:
|
Tile ids are used directly:
|
||||||
|
|
||||||
- **Server** — `tile_flags(id)` (in `map.rs`) maps an id to gameplay `TileFlags`
|
- **Sim** — `tile_flags(id)` (in `map.rs`) maps an id to gameplay `TileFlags`
|
||||||
(collidable / opaque). Hand-maintained vocabulary; currently everything is walkable floor.
|
(collidable / opaque) via the `sim::tile_collidable` vocabulary. Hand-maintained; test values only.
|
||||||
- **Client** — the id indexes straight into `overworld.tga` (`game.rs`). The tileset's tile
|
- **Renderer** — the id indexes straight into `overworld.png` (`game.rs`). The tileset's tile
|
||||||
order *is* the id space; keep the Tiled tileset and `overworld.tga` in lockstep.
|
order *is* the id space; keep the Tiled tileset and `overworld.png` in lockstep.
|
||||||
|
|
||||||
No `firstgid` subtraction: the authored ids already line up with `overworld.tga`.
|
No `firstgid` subtraction: the authored ids already line up with `overworld.png`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## World assembly
|
## World assembly
|
||||||
|
|
||||||
`load_world` slices the map into 32×32 chunks via `Chunk::generate`, which builds each chunk's
|
`load_world` slices the map into 32×32 chunks via `Chunk::generate`. A chunk is a plain
|
||||||
6-bit palette automatically. The map's top-left tile sits at world (0, 0).
|
`[u16; 1024]` of tile ids; flags are derived from the id on lookup (`map::tile_flags`). That
|
||||||
|
array is also the natural on-disk layout should chunks ever be streamed from disk (RLE over
|
||||||
|
the `u16`s would do) — the CSV stays the interchange format for external tooling. The map's
|
||||||
|
top-left tile sits at world (0, 0). Loading a 256×256 map takes well under a millisecond in
|
||||||
|
release; the map path is the game's first CLI argument (`game assets/map_large`).
|
||||||
|
|
||||||
**Edge border:** a 30×30 map only fills part of chunk (0,0) (which spans tiles 0–31). Tiles
|
**Edge border:** a 30×30 map only fills part of chunk (0,0) (which spans tiles 0–31). Tiles
|
||||||
inside a loaded chunk but outside the authored map are emitted as an *invisible solid border*
|
inside a loaded chunk but outside the authored map are emitted as an *invisible solid border*
|
||||||
|
|||||||
@@ -1,324 +0,0 @@
|
|||||||
# Protocol
|
|
||||||
Wire format for client-server communication.
|
|
||||||
|
|
||||||
## Transport
|
|
||||||
|
|
||||||
- UDP (`std::net::UdpSocket`, non-blocking)
|
|
||||||
- Hard limit: **1200 bytes per datagram** (safe internet MTU; avoids IP fragmentation)
|
|
||||||
- All integers: little-endian
|
|
||||||
- Server streams `StatePackets` continuously; client sends `ActionPackets` only when needed
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Header
|
|
||||||
|
|
||||||
Every datagram begins with a 6-byte universal header.
|
|
||||||
|
|
||||||
| Offset | Size | Type | Field | Notes |
|
|
||||||
|--------|------|------|---------------|-----------------------------------------|
|
|
||||||
| 0 | 2 | u16 | `magic` | Fixed: `0x524C` ("RL") — rejects noise |
|
|
||||||
| 2 | 1 | u8 | `version` | Protocol version — server rejects mismatch |
|
|
||||||
| 3 | 1 | u8 | `packet_type` | See packet type table below |
|
|
||||||
| 4 | 2 | u16 | `client_type` | 0 = official client; others registered |
|
|
||||||
|
|
||||||
`client_type` allows the server to distinguish official clients, registered bots, and
|
|
||||||
forks for analytics and access control, without affecting the protocol logic.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Packet types
|
|
||||||
|
|
||||||
| Packet type | id | Flow | Size |
|
|
||||||
|---------------|----|------------------|------------------|
|
|
||||||
| State | 0 | server → client | 72 bytes |
|
|
||||||
| Action | 1 | client → server | min. 74 bytes |
|
|
||||||
| Chunk Data | 2 | server → client | max. 909 bytes |
|
|
||||||
| Entity | 3 | server → client | max. 1200 bytes |
|
|
||||||
| Entity Query | 4 | client → server | 18 bytes |
|
|
||||||
| Entity Detail | 5 | server → client | max. 1200 bytes |
|
|
||||||
| Ping | 6 | client → server | 14 bytes |
|
|
||||||
| Pong | 7 | server → client | 14 bytes |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### StatePacket — server → client
|
|
||||||
|
|
||||||
Sent by the server at 10 Hz regardless of client activity.
|
|
||||||
Currently carries the chunk manifest for the 3×3 neighbourhood around the player.
|
|
||||||
|
|
||||||
| Offset | Size | Type | Field | Notes |
|
|
||||||
|--------|------|------------|---------------------|-------------------------------------------------|
|
|
||||||
| 0 | 6 | Header | `header` | packet_type = 0 |
|
|
||||||
| 6 | 4 | u32 | `tick` | Server tick counter |
|
|
||||||
| 10 | 54 | ChunkEntry | `chunks[9]` | 9 chunk entries |
|
|
||||||
| 64 | 4 | u32 | `player_entity_id` | Global entity ID of the receiving client |
|
|
||||||
| 68 | 4 | u32 | `entity_checksum` | FNV-1a over all EntityPacket payloads this tick |
|
|
||||||
|
|
||||||
**Total: 72 bytes**
|
|
||||||
|
|
||||||
The `entity_checksum` lets the client detect a lost `EntityPacket` without a dedicated
|
|
||||||
ACK: if the checksum differs from the one computed over the last received entity update,
|
|
||||||
the client knows to retransmit an `ActionPacket` (`target_tick = 0`, no-op action) to
|
|
||||||
prompt the server to re-send the current entity state.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ActionPacket — client → server
|
|
||||||
|
|
||||||
Sent by the client on player action or on a chunk cache miss.
|
|
||||||
|
|
||||||
| Offset | Size | Type | Field | Notes |
|
|
||||||
|--------|------|--------------|-----------------|--------------------------------|
|
|
||||||
| 0 | 6 | Header | `header` | packet_type = 1 |
|
|
||||||
| 6 | 8 | u64 | `auth_token` | Token of the current session |
|
|
||||||
| 14 | 4 | u32 | `target_tick` | Tick the action is scheduled for (see below); 0 = keep-alive/ack only |
|
|
||||||
| 18 | 54 | ChunkEntry | `cache[9]` | Versions client currently holds |
|
|
||||||
| 72 | 2 | PlayerAction | `player_action` | Derived from user input |
|
|
||||||
| 74 | ? | ActionData | `action_data` | Dependent on PlayerAction |
|
|
||||||
|
|
||||||
**Minimum: 74 bytes** (no ActionData)
|
|
||||||
|
|
||||||
**Tick-addressed scheduling.** Actions are scheduled onto the server's tick timeline
|
|
||||||
instead of being consumed in arrival order. `target_tick` selects the movement window
|
|
||||||
(`target_tick / TICKS_PER_MOVE`, rounded up) the action executes in:
|
|
||||||
|
|
||||||
- A second action addressed to the same window **replaces** the first — this is how the
|
|
||||||
client retracts (NOOP) or changes a scheduled step until its window executes, and how
|
|
||||||
retransmits dedupe for free.
|
|
||||||
- A **late** action (window already passed on arrival) moves to the next window, but
|
|
||||||
only if that slot is empty: late actions fill gaps, they never override newer intent.
|
|
||||||
- Only the next `ACTION_WINDOW_HORIZON` windows are addressable; anything beyond is
|
|
||||||
dropped. Combined with one-action-per-window execution this bounds server memory and
|
|
||||||
movement speed regardless of client behavior.
|
|
||||||
- `target_tick = 0` carries no scheduling intent (keep-alive / cache-ack packets).
|
|
||||||
|
|
||||||
**ChunkEntry (6 bytes)**
|
|
||||||
|
|
||||||
| Offset | Size | Type | Field |
|
|
||||||
|--------|------|------|------------|
|
|
||||||
| 0 | 4 | u32 | `chunk_id` |
|
|
||||||
| 4 | 2 | u16 | `version` |
|
|
||||||
|
|
||||||
`chunk_id` encodes the chunk grid position: `(x as u16) | ((y as u16) << 16)`,
|
|
||||||
where x and y are signed chunk coordinates (i16 each).
|
|
||||||
|
|
||||||
**PlayerAction (u16)**
|
|
||||||
|
|
||||||
| Value | Action |
|
|
||||||
|-------|---------|
|
|
||||||
| 0 | No-Op (cache update only) |
|
|
||||||
| 1 | North |
|
|
||||||
| 2 | East |
|
|
||||||
| 3 | South |
|
|
||||||
| 4 | West |
|
|
||||||
|
|
||||||
$TODO — additional actions (interact, etc.)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ChunkPacket — server → client
|
|
||||||
|
|
||||||
Sent by the server for each chunk the client is missing or has stale.
|
|
||||||
One chunk per datagram.
|
|
||||||
|
|
||||||
The world is divided into **32×32 tile chunks**.
|
|
||||||
- At most **4 chunks (2×2)** are visible at once.
|
|
||||||
- The 3×3 neighbourhood (9 chunks) covers all prefetch needs.
|
|
||||||
- Each chunk carries a **local palette** of up to 64 tile types.
|
|
||||||
- Tiles are encoded as 6-bit palette indices (4 tiles per 3 bytes, no padding).
|
|
||||||
|
|
||||||
| Offset | Size | Type | Field | Notes |
|
|
||||||
|-------------------|----------|--------------|-------------|---------------------------------|
|
|
||||||
| 0 | 6 | Header | `header` | packet_type = 2 |
|
|
||||||
| 6 | 6 | ChunkEntry | `chunk` | ID and version of this chunk |
|
|
||||||
| 12 | 1 | u8 | `pal_count` | Number of palette entries (≤64) |
|
|
||||||
| 13 | max. 128 | u16[] | `palette` | Global tile IDs, pal_count × 2 |
|
|
||||||
| 13 + pal_count×2 | 768 | packed u6[] | `tiles` | 1024 tiles, 6-bit indices |
|
|
||||||
|
|
||||||
**Maximum: 6 + 6 + 1 + 128 + 768 = 909 bytes**
|
|
||||||
|
|
||||||
**Palette** — up to 64 entries, each a global tile ID (u16) mapping local 6-bit index → world tile type.
|
|
||||||
|
|
||||||
**Tiles** — 1024 tiles packed as 256 groups of 3 bytes (4 tiles × 6 bits = 24 bits per group).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### EntityPacket — server → client
|
|
||||||
|
|
||||||
Sent by the server each tick, immediately after the `StatePacket`.
|
|
||||||
Contains the bulk entity update for all entities within the visible **30×30 tile viewport**.
|
|
||||||
Positions are **absolute world tile coordinates**.
|
|
||||||
|
|
||||||
If the entity count exceeds what fits in one datagram, the server sends multiple
|
|
||||||
`EntityPacket`s on the same tick; `packet_flags` bit 0 signals that more follow.
|
|
||||||
|
|
||||||
| Offset | Size | Type | Field | Notes |
|
|
||||||
|--------|----------------|-------------|----------------|--------------------------------|
|
|
||||||
| 0 | 6 | Header | `header` | packet_type = 3 |
|
|
||||||
| 6 | 4 | u32 | `tick` | Matches the `StatePacket` tick |
|
|
||||||
| 10 | 1 | u8 | `entity_count` | Entities in this datagram |
|
|
||||||
| 11 | 1 | u8 | `packet_flags` | bit 0: more packets follow |
|
|
||||||
| 12 | entity_count×… | EntityEntry | `entities` | Variable-length entries |
|
|
||||||
|
|
||||||
**Maximum: 1200 bytes** — without metadata: ⌊(1200 − 12) / 18⌋ = **66 entities per datagram**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### EntityEntry
|
|
||||||
|
|
||||||
| Offset | Size | Type | Field | Notes |
|
|
||||||
|--------|------------|------|-----------------|-----------------------------------------|
|
|
||||||
| 0 | 4 | u32 | `id` | Server-assigned entity ID (persistent) |
|
|
||||||
| 4 | 2 | u16 | `type_id` | Entity type; 0 = player |
|
|
||||||
| 6 | 2 | i16 | `pos_x` | Absolute world tile X |
|
|
||||||
| 8 | 2 | i16 | `pos_y` | Absolute world tile Y |
|
|
||||||
| 10 | 2 | u16 | `hp` | Current HP |
|
|
||||||
| 12 | 2 | u16 | `hp_max` | Max HP |
|
|
||||||
| 14 | 2 | u16 | `elo` | Elo rating (acts as level) |
|
|
||||||
| 16 | 1 | u8 | `entity_flags` | See flag table below |
|
|
||||||
| 17 | 1 | u8 | `meta_len` | Byte length of the TLV metadata block |
|
|
||||||
| 18 | `meta_len` | u8[] | `meta` | TLV metadata (0 bytes if none) |
|
|
||||||
|
|
||||||
**Base size: 18 bytes** (plus `meta_len` bytes of metadata).
|
|
||||||
|
|
||||||
**entity_flags:**
|
|
||||||
|
|
||||||
| Bit | Meaning |
|
|
||||||
|-----|-------------------------------------------------------|
|
|
||||||
| 0 | `is_static` — does not move (chest, sign, item stack) |
|
|
||||||
| 1 | `is_hostile` |
|
|
||||||
| 2 | `is_interactable` — show interaction prompt |
|
|
||||||
| 3–7 | reserved |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Inline metadata — TLV format
|
|
||||||
|
|
||||||
Inline metadata is a sequence of TLV (type–length–value) fields appended directly after
|
|
||||||
the fixed `EntityEntry` fields. `meta_len = 0` means no metadata is present.
|
|
||||||
|
|
||||||
Each TLV field:
|
|
||||||
|
|
||||||
| Offset | Size | Type | Field |
|
|
||||||
|--------|------|------|---------|
|
|
||||||
| 0 | 1 | u8 | `tag` |
|
|
||||||
| 1 | 1 | u8 | `len` |
|
|
||||||
| 2 | len | u8[] | `value` |
|
|
||||||
|
|
||||||
**Defined tags:**
|
|
||||||
|
|
||||||
| Tag | Name | Value format | Notes |
|
|
||||||
|------|------------------|--------------|--------------------------------|
|
|
||||||
| 0x01 | `status_effects` | u16 bitmask | Active status effects |
|
|
||||||
| 0x02 | `weapon` | u16 type_id | Currently equipped weapon |
|
|
||||||
| 0x03 | `armor` | u16 type_id | Currently equipped armor |
|
|
||||||
|
|
||||||
Large metadata (chest inventory, sign text, dialogue trees) is not included inline.
|
|
||||||
Request it via `EntityQueryPacket` (type 4) on player interaction.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### EntityQueryPacket — client → server
|
|
||||||
|
|
||||||
Sent when the client needs detailed metadata for a specific entity (on interaction,
|
|
||||||
hover, or similar trigger). The server responds with an `EntityDetailPacket`.
|
|
||||||
|
|
||||||
| Offset | Size | Type | Field | Notes |
|
|
||||||
|--------|------|--------|--------------|------------------------------|
|
|
||||||
| 0 | 6 | Header | `header` | packet_type = 4 |
|
|
||||||
| 6 | 8 | u64 | `auth_token` | Token of the current session |
|
|
||||||
| 14 | 4 | u32 | `entity_id` | Entity to query |
|
|
||||||
|
|
||||||
**Total: 18 bytes**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### EntityDetailPacket — server → client
|
|
||||||
|
|
||||||
Full metadata response for a queried entity. Sent in reply to an `EntityQueryPacket`.
|
|
||||||
|
|
||||||
| Offset | Size | Type | Field | Notes |
|
|
||||||
|--------|------------|--------|--------------|------------------------------|
|
|
||||||
| 0 | 6 | Header | `header` | packet_type = 5 |
|
|
||||||
| 6 | 4 | u32 | `tick` | Server tick at time of query |
|
|
||||||
| 10 | 4 | u32 | `entity_id` | Entity being described |
|
|
||||||
| 14 | 2 | u16 | `meta_len` | Byte length of metadata |
|
|
||||||
| 16 | `meta_len` | u8[] | `meta` | Full TLV metadata block |
|
|
||||||
|
|
||||||
**Maximum: 1200 bytes** (up to 1184 bytes of metadata).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### PingPacket / PongPacket — round-trip measurement
|
|
||||||
|
|
||||||
Liveness and RTT probe. The client sends a `PingPacket`; the server echoes the payload back
|
|
||||||
unchanged as a `PongPacket`. Both share the same layout.
|
|
||||||
|
|
||||||
| Offset | Size | Type | Field | Notes |
|
|
||||||
|--------|------|--------|----------------|--------------------------------------|
|
|
||||||
| 0 | 6 | Header | `header` | packet_type = 6 (Ping) / 7 (Pong) |
|
|
||||||
| 6 | 8 | u64 | `timestamp_ms` | Echoed verbatim by the server |
|
|
||||||
|
|
||||||
**Total: 14 bytes**
|
|
||||||
|
|
||||||
The client currently measures RTT locally via the elapsed time since the ping was sent, so
|
|
||||||
`timestamp_ms` is sent as 0; the field is reserved for server-stamped timing if needed later.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Protocol flow
|
|
||||||
|
|
||||||
```
|
|
||||||
── every tick (10 Hz) ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
server → client StatePacket (tick N, chunk manifest, entity_checksum)
|
|
||||||
server → client EntityPacket (tick N, entities 0–73)
|
|
||||||
server → client EntityPacket (tick N, entities 74–N, more=0) ← if needed
|
|
||||||
|
|
||||||
↓ client verifies entity_checksum
|
|
||||||
↓ checksum mismatch → retransmit ActionPacket (no-op)
|
|
||||||
↓ chunk cache miss on chunk X →
|
|
||||||
|
|
||||||
client → server ActionPacket (auth_token, seq, cache state, player_action)
|
|
||||||
|
|
||||||
server → client ChunkPacket (chunk X, full data)
|
|
||||||
server → client ChunkPacket (chunk Y, full data) ← if multiple misses
|
|
||||||
|
|
||||||
── on player interaction ─────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
client → server EntityQueryPacket (auth_token, entity_id)
|
|
||||||
server → client EntityDetailPacket (entity_id, full TLV metadata)
|
|
||||||
```
|
|
||||||
|
|
||||||
The server reads the `ActionPacket`'s cache list, computes the diff against current
|
|
||||||
chunk versions, and sends one `ChunkPacket` per missing or stale chunk.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Reliability
|
|
||||||
|
|
||||||
UDP is unreliable. The protocol handles loss without a dedicated ACK mechanism:
|
|
||||||
|
|
||||||
**Lost ActionPacket** — the server never learns of the cache miss and keeps streaming
|
|
||||||
StatePackets. The client retransmits the ActionPacket after **200–300 ms** if no
|
|
||||||
ChunkPacket has arrived.
|
|
||||||
|
|
||||||
**Lost ChunkPacket** — the server has already processed the ActionPacket and will not
|
|
||||||
retransmit spontaneously. The client's timeout fires (200–300 ms), it resends the
|
|
||||||
ActionPacket with its updated cache state (listing only still-missing chunks), and
|
|
||||||
the server sends the missing chunks again.
|
|
||||||
|
|
||||||
**Duplicate ActionPackets** — the server handles these idempotently. The cache list is
|
|
||||||
self-describing state; the server reads it, diffs against current versions, and sends
|
|
||||||
whatever is still missing. No deduplication logic is required.
|
|
||||||
|
|
||||||
**Lost StatePacket** — the next tick delivers the same chunk manifest. No retry needed;
|
|
||||||
the client simply waits one tick (~100 ms).
|
|
||||||
|
|
||||||
**Lost EntityPacket** — detected via the `entity_checksum` in the next `StatePacket`.
|
|
||||||
The client retransmits a no-op `ActionPacket` (`target_tick = 0`, no-op action,
|
|
||||||
current cache state); the server treats this as a normal diff request and re-sends the
|
|
||||||
full entity update for the tick.
|
|
||||||
|
|
||||||
**Lost EntityQueryPacket / EntityDetailPacket** — the client retransmits the query
|
|
||||||
after a 200–300 ms timeout if no `EntityDetailPacket` has arrived.
|
|
||||||
+80
-154
@@ -1,129 +1,66 @@
|
|||||||
# Roadmap
|
# Roadmap
|
||||||
|
|
||||||
Workspace layout: `shared/` (wire types), `server/` (authoritative sim), `client/` (render
|
Local single-player roguelike with a continuously simulated, tick-based world.
|
||||||
terminal). The platform layer — window, GPU, input, RGB332 palette — lives in the external
|
|
||||||
`pbio` crate (git dependency), not in this repo.
|
Workspace layout: `sim/` (headless world simulation — tiles, entities, movement rules,
|
||||||
|
tick loop; no platform dependencies, unit-testable) and `game/` (the executable — window,
|
||||||
|
rendering, input, pathfinding, camera; owns a `Sim` and drives it). The platform layer —
|
||||||
|
window, GPU, input, RGB565 framebuffer — lives in the external `pbio` crate (git dependency,
|
||||||
|
branch `truecolor16`),
|
||||||
|
not in this repo.
|
||||||
|
|
||||||
|
History: this fork started from a client/server multiplayer architecture (authoritative UDP
|
||||||
|
server, thin client with prediction). The scope was cut to single player in September 2026;
|
||||||
|
the sim kept its authoritative shape, everything network-related was removed. The `sim`
|
||||||
|
crate's `tick`/`set_action` interface is the seam a server could be wrapped around again.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Achieved milestones
|
## Achieved milestones
|
||||||
|
|
||||||
- [x] TGA loader: `Image`, `from_tga`, `to_tileset`, `decode_rle` in `client/src/assets.rs`
|
- [x] PNG loader: `Image::from_png` (via the `png` crate, quantized to RGB565 on load, alpha
|
||||||
- [x] Pixel helpers: `set_pixel`, `extract`, `blit`, `blit_tile` in `client/src/game/pixelhelper.rs`
|
mask), `to_tileset` → `Vec<Tile>` in `game/src/assets.rs`. Framebuffer is 16-bit
|
||||||
— signed `i32` offsets, clip against all four framebuffer edges, index 0 = transparent
|
truecolor (`pbio` branch `truecolor16`)
|
||||||
- [x] Input system: `GameAction`, `InputMap`, `InputState` in `client/src/input.rs` (over `pbio::Key`)
|
- [x] Pixel helpers: `set_pixel`, `extract`, `blit`, `blit_tile` in `game/src/game/pixelhelper.rs`
|
||||||
- [x] Protocol spec: `notes/protocol.md` — Header, State, Action, Chunk, Entity, EntityQuery,
|
— signed `i32` offsets, clip against all four framebuffer edges, per-tile opacity mask
|
||||||
EntityDetail, Ping/Pong; reliability model
|
- [x] Input system: `GameAction`, `InputMap`, `InputState` in `game/src/input.rs` (over `pbio::Key`)
|
||||||
- [x] Shared crate: packet structs as `bytemuck::Pod` with compile-time size assertions,
|
- [x] World model: `World` of 32×32 `Chunk`s (plain `[u16; 1024]` tile ids, flags derived
|
||||||
`player_action` / `packet_type` constants, `chunk_id` / `chunk_coords`
|
on lookup — the network-era 64-entry palette is gone), entities indexed per chunk,
|
||||||
- [x] Server binary: 24 Hz tick loop, 3×3-chunk test world (`build_world`), wall collision,
|
`entities_in_viewport`. Map path is the first CLI argument; `assets/map_large` is a
|
||||||
per-client action queue, `ChunkPacket` dispatch on cache miss, stale-client eviction
|
generated 256×256 stress map
|
||||||
- [x] Client net: `NetClient` in `client/src/net.rs`, non-blocking UDP recv,
|
- [x] Map loader: Tiled-CSV parser in `sim/src/map.rs` (flip-flag masking, content-derived
|
||||||
`StatePacket` / `EntityPacket` / `ChunkPacket` / `PongPacket` dispatch, entity-checksum retransmit
|
|
||||||
- [x] End-to-end loop: player moves on server, position reflected in `EntityPacket`, rendered on client
|
|
||||||
- [x] Tile rendering: chunk-based world rendering from received `ChunkPacket` data (6-bit unpack)
|
|
||||||
- [x] Tick-based movement: movement resolved in `entity_tick`, currently every 4th tick (`sim.rs`)
|
|
||||||
- [x] Sprite-based entity rendering: entity `type_id` → tile in `entities.tga`, blitted with transparency
|
|
||||||
- [x] Phase-staggered broadcast: per-client `send_phase`, flat outbound load across ticks
|
|
||||||
- [x] Ping/Pong RTT measurement (`PingPacket` / `PongPacket`)
|
|
||||||
- [x] Connection timeout: client emits `Disconnected` after 10 s without a `StatePacket`;
|
|
||||||
server evicts clients unseen for 10 s
|
|
||||||
- [x] Map loader: Tiled-CSV parser in `server/src/map.rs` (flip-flag masking, content-derived
|
|
||||||
dimensions), `load_world` slices it into chunks, invisible solid border at the map rim.
|
dimensions), `load_world` slices it into chunks, invisible solid border at the map rim.
|
||||||
Replaces the hardcoded `build_world`. See `notes/07-map-loader.md`
|
See `notes/07-map-loader.md`
|
||||||
- [x] Client movement prediction: queued unconfirmed steps (`path` in `client/src/game.rs`),
|
- [x] Tile rendering: chunk-based world rendering straight from `World` (`render_viewport`)
|
||||||
paced at the server's 6 Hz movement cadence, reconciled against `EntityPacket` positions,
|
- [x] Sprite-based entity rendering: entity `type_id` → tile in `entities.png`, blitted with transparency
|
||||||
dropped after idle timeout. Shared collision vocabulary `shared::tile_collidable`
|
- [x] Hybrid tick loop: 24 Hz base tick, movement in `entity_tick` every 4th tick (6 Hz);
|
||||||
(currently empty — every tile walkable) keeps client prediction and server sim in lockstep.
|
the game loop drives the sim with a fixed-timestep accumulator. See `notes/06-hybrid-tick-loop.md`
|
||||||
- [x] Click-to-move: framebuffer click → world tile, A* over the chunk cache
|
- [x] Intent model: one scheduled action per entity per movement window (`Sim::set_action`),
|
||||||
(`client/src/game/pathfind.rs`, unknown chunks count as blocked), route translated into
|
replaced on re-set, consumed on execution. Headless tests in `sim/src/sim.rs`
|
||||||
cardinal actions one step per movement interval — the server only ever sees movement
|
|
||||||
actions and stays authoritative. Keyboard input cancels the route; each step is
|
|
||||||
re-validated at send time and a blocked step voids the route. In-flight steps render
|
|
||||||
bright blue, planned route dim blue. Click-and-hold steers continuously: while the
|
|
||||||
button is held the route keeps replanning toward the tile under the cursor (only
|
|
||||||
when that tile changes — cursor or camera movement), sweeping across blocked tiles
|
|
||||||
keeps the current route, and a voided route replans automatically while held.
|
|
||||||
- [x] 8-directional movement in chessboard geometry: world physics use the Chebyshev
|
- [x] 8-directional movement in chessboard geometry: world physics use the Chebyshev
|
||||||
metric — diagonal and cardinal steps are the same distance, a "circle" is a square
|
metric — diagonal and cardinal steps are the same distance, a "circle" is a square
|
||||||
of tiles, matching the square viewport. Four diagonal actions in
|
of tiles, matching the square viewport. The single-step rule lives in
|
||||||
`shared::player_action`; the single-step rule lives in `shared::step_allowed`
|
`sim::step_allowed` (king move onto a free tile, diagonals additionally need both
|
||||||
(king move onto a free tile, diagonals additionally need both orthogonal neighbors
|
orthogonal neighbors free — no corner cutting) and is the one function used by the
|
||||||
free — no corner cutting) and is the one function used by the server sim, client
|
sim and the A*. Two held keys walk diagonally.
|
||||||
send-time validation and the client A* (8-connected, Chebyshev heuristic). Two held
|
- [x] Collision vocabulary seeded with test values (`sim::tile_collidable`): id 146
|
||||||
keys walk diagonally.
|
(trees/rocks) and id 0 (empty / the invisible world border). A proper tile-data file
|
||||||
- [x] Collision vocabulary seeded with test values (`shared::tile_collidable`): id 146
|
format replaces this table later.
|
||||||
(trees/rocks) and id 0 — id 0 doubles as the server's invisible world border, which
|
- [x] Click-to-move: framebuffer click → world tile, A* over the world
|
||||||
the client previously mispredicted as walkable. A proper tile-data file format
|
(`game/src/game/pathfind.rs`, 8-connected, Chebyshev heuristic), route fed into the
|
||||||
replaces this table later.
|
sim one step per movement window. Keyboard input cancels the route; each step is
|
||||||
- [x] Tick-addressed action scheduling (supersedes two interim designs — a sequence-
|
re-validated at scheduling time and a blocked step replans toward the persistent
|
||||||
deduped FIFO queue and its flow control — that fixed a periodic walking hitch and
|
goal. Click-and-hold steers continuously: while the button is held the route keeps
|
||||||
a path/route deadlock but kept two free-running clocks racing each other). Actions
|
replanning toward the tile under the cursor (only when that tile changes — cursor
|
||||||
are now scheduled onto the server's tick timeline: `ActionPacket.target_tick`
|
or camera movement), sweeping across blocked tiles keeps the current route.
|
||||||
(formerly `sequence`) selects the movement window, a second action to the same
|
- [x] Smooth camera follow: the camera is a float pixel position (`cam` in `game.rs`)
|
||||||
window *replaces* the first (retraction via NOOP, rescheduling, retransmit dedup),
|
that tracks the player linearly at ~64 px/s, snapping only to whole pixels at
|
||||||
late actions fill only an *empty* next window (gap-filling without overriding
|
render time (and outright on teleport-sized corrections). The viewport renders
|
||||||
newer intent — needed over real internet links so actions don't die pointlessly),
|
with sub-tile offsets (31×31 tile pass + right-strip clip).
|
||||||
and only `ACTION_WINDOW_HORIZON = 3` future windows are addressable. The sim keeps
|
- [x] Entity interpolation: per-id lerp table (`EntityLerp` in `game.rs`) — previous/current
|
||||||
per-entity window slot maps and executes at most one action per window, so floods
|
tile plus a clock, rendered as a pixel lerp over one movement interval. Jumps of
|
||||||
can neither grow memory nor speed anyone up. The client estimates the server tick
|
more than one tile (Chebyshev) snap. Purely cosmetic; game logic keeps using the
|
||||||
from `StatePacket.tick` plus elapsed time and schedules each step into the next
|
sim's tile positions.
|
||||||
window — one send per window by construction, no local send timer, no clock-rate
|
|
||||||
race. `target_tick = 0` marks keep-alive/cache-ack packets with no scheduling
|
|
||||||
intent. See `notes/protocol.md` (ActionPacket).
|
|
||||||
- [x] Client-side unexpected-state handling: the movement goal is persistent (outlives
|
|
||||||
the planned route) and every surprise reroutes toward it — a blocked route step
|
|
||||||
replans instead of voiding the plan, and a confirmed position off the predicted
|
|
||||||
path (lost/rejected/overridden step) retracts all still-scheduled steps (NOOP to
|
|
||||||
their windows), drops the stale prediction and replans from the confirmed tile.
|
|
||||||
Changing plans mid-run (new click/steer target) likewise retracts scheduled-but-
|
|
||||||
unexecuted steps, so old intent stops playing out on the server within a window.
|
|
||||||
The goal is released on arrival, unreachability, keyboard override, or a discrete
|
|
||||||
click on an unreachable tile. This is the "reactive replanning" item formerly
|
|
||||||
parked under Later — Robustness.
|
|
||||||
- [x] `netsim` — bad-internet simulator (workspace member, dev tool): a UDP proxy adding
|
|
||||||
delay, jitter and loss per direction (`--delay/--jitter/--loss`, `--up-*`/`--down-*`
|
|
||||||
overrides; reordering emerges from jitter). No root, game-traffic only, zero deps.
|
|
||||||
The client takes an optional server address argument to point at it:
|
|
||||||
`cargo run -p netsim -- --delay 80 --jitter 30 --loss 5` + `client 127.0.0.1:7778`.
|
|
||||||
- [x] Bad-link hardening (netsim immediately broke the naive scheduling — locks under
|
|
||||||
isolated loss 15% and isolated delay 120 ms):
|
|
||||||
(a) RTT-adaptive scheduling lead — the client pings automatically (1 Hz, smoothed),
|
|
||||||
and schedules `ceil((rtt + margin) / window)` windows ahead instead of always one:
|
|
||||||
under systematic latency "late" had been the *normal* case. The retraction horizon
|
|
||||||
moves out the same way (a cancellation needs the same lead an action does).
|
|
||||||
(b) Late-rule cleanup in the sim — a late movement action keeps its *order*, not
|
|
||||||
its time (first still-empty upcoming window), so bunched late arrivals no longer
|
|
||||||
collapse onto one slot and eat each other; late NOOPs are dropped outright (as
|
|
||||||
gap-fillers they used to block real steps: retract → late NOOPs poison upcoming
|
|
||||||
windows → replanned steps eaten → retract again — a lock loop).
|
|
||||||
(c) Client stall watchdog — steps in flight but nothing confirmed for 750 ms means
|
|
||||||
the prediction is dead no matter why (e.g. *all* in-flight actions lost: the server
|
|
||||||
never moves, so the moved-off-plan desync detection never fires, path stays full,
|
|
||||||
nothing is ever sent again): retract, drop, replan toward the goal.
|
|
||||||
`PATH_MAX_LEN` is back to 8 as a pure prediction bound — server safety now comes
|
|
||||||
from window addressing, and on a slow link several correct steps are legitimately
|
|
||||||
unconfirmed at once (confirmations lag a full RTT).
|
|
||||||
- [x] Prediction rebuilt as predict → ack → replay (replaces the per-problem patches
|
|
||||||
above with structural robustness; fixed multi-second replan storms at high ping).
|
|
||||||
The old model stored absolute predicted tiles and reconciled by tile matching, so
|
|
||||||
any surprise "invalidated" the whole prediction and recovery meant clear + replan
|
|
||||||
from the confirmed position — but steps inside the retraction horizon cannot be
|
|
||||||
cancelled and still execute ("zombies"), shifting the server off every fresh plan
|
|
||||||
and re-triggering recovery in a loop. Now: pending steps are `(window, delta)`
|
|
||||||
pairs, `EntityPacket.tick` is the acknowledgment cursor (every window ≤ tick/4 is
|
|
||||||
provably consumed — executed, rejected or lost, it no longer matters which), and
|
|
||||||
the predicted position is always *derived* by replaying pending deltas on top of
|
|
||||||
the confirmed position. A surprise shifts the prediction instead of killing it; a
|
|
||||||
route that no longer connects triggers one clean replan toward the persistent goal
|
|
||||||
via the existing send-time validation. Deleted outright: tile-matching reconcile,
|
|
||||||
the moved-off-plan desync heuristic, the stall watchdog, and the idle path drop —
|
|
||||||
acked windows expire pending steps automatically, so the path cannot go stale.
|
|
||||||
The replay applies each pending delta through the shared `step_allowed` rule —
|
|
||||||
exactly as the server will — so a delta the server is going to reject does not
|
|
||||||
move the prediction either, and the predicted position can never sit inside a
|
|
||||||
wall (previously a diverged prediction could, causing a brief walk-into-wall
|
|
||||||
lock until the acks caught up).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -131,13 +68,10 @@ terminal). The platform layer — window, GPU, input, RGB332 palette — lives i
|
|||||||
|
|
||||||
### 06 — Hybrid tick loop *(partially done)*
|
### 06 — Hybrid tick loop *(partially done)*
|
||||||
|
|
||||||
Design of record: **`notes/06-hybrid-tick-loop.md`** (24 Hz base tick + stride scheduling).
|
Design of record: **`notes/06-hybrid-tick-loop.md`**.
|
||||||
This supersedes the original 20/10/1 Hz three-tier sketch.
|
|
||||||
|
|
||||||
- [x] 24 Hz base loop (`server/src/main.rs`)
|
- [x] 24 Hz base loop driven from the game loop
|
||||||
- [x] Phase-staggered broadcast at 12 Hz/client (`send_phase` in `server/src/net.rs`)
|
- [x] Stride layout settled: `entity_tick` at `%4` (6 Hz, sets movement rate)
|
||||||
- [x] Stride layout settled: `entity_tick` at `%4` (6 Hz, sets movement rate), broadcast at
|
|
||||||
`%2` (12 Hz/client). The `06` note now documents this and why.
|
|
||||||
- [ ] Action-point model (`energy` / `speed` per entity, act when `energy >= ACTION_COST`).
|
- [ ] Action-point model (`energy` / `speed` per entity, act when `energy >= ACTION_COST`).
|
||||||
Removed from `entity.rs` for now as premature — reintroduce when NPCs (08) actually need it.
|
Removed from `entity.rs` for now as premature — reintroduce when NPCs (08) actually need it.
|
||||||
Then `entity_tick` can move to `%2` (12 Hz) and `speed` sets the effective movement rate.
|
Then `entity_tick` can move to `%2` (12 Hz) and `speed` sets the effective movement rate.
|
||||||
@@ -148,66 +82,61 @@ This supersedes the original 20/10/1 Hz three-tier sketch.
|
|||||||
|
|
||||||
Remaining follow-ups when the need is concrete:
|
Remaining follow-ups when the need is concrete:
|
||||||
|
|
||||||
- Collision vocabulary: seeded with test values (0, 146) in `shared::tile_collidable`.
|
- Collision vocabulary: seeded with test values (0, 146) in `sim::tile_collidable`.
|
||||||
Decide on a proper file format for tile data (collision, opacity, …) instead of a
|
Decide on a proper file format for tile data (collision, opacity, …) instead of a
|
||||||
hardcoded match, then feed both server and client from it.
|
hardcoded match.
|
||||||
- Tile flipping: orientation is discarded on load; revisit with the sprite pass (09).
|
- Tile flipping: orientation is discarded on load; revisit with the sprite pass (09).
|
||||||
- Multiple / object layers (spawns, triggers) — not yet parsed.
|
- Multiple / object layers (spawns, triggers) — not yet parsed. The player currently
|
||||||
|
spawns at (0, 0).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 08 — Basic NPC entity + AI budget
|
### 08 — Basic NPC entity + AI budget
|
||||||
|
|
||||||
One dumb wandering enemy. Validates the simulation architecture before complexity accumulates.
|
One dumb wandering enemy. Validates the simulation architecture before complexity accumulates.
|
||||||
Depends on the action-point model from 06 being reintroduced.
|
Depends on the action-point model from 06 being reintroduced. NPCs schedule intents through
|
||||||
|
the same `Sim::set_action` the player uses.
|
||||||
|
|
||||||
- Add `EntityKind::Npc` with a `think() -> u32` method returning budget cost
|
- Add `EntityKind::Npc` with a `think() -> u32` method returning budget cost
|
||||||
- Per sim-tick: distribute `think_budget = BASE / (clients + 1)` across entities ordered by player proximity
|
- Per sim-tick: distribute a fixed `think_budget` across entities ordered by player proximity
|
||||||
- Complex entities consume more budget; simple ones less. Loop breaks at zero — natural load shedding.
|
- Complex entities consume more budget; simple ones less. Loop breaks at zero — natural load shedding.
|
||||||
- No framework. No trait objects yet. A match on `EntityKind` is fine.
|
- No framework. No trait objects yet. A match on `EntityKind` is fine.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let mut budget: u32 = BASE_BUDGET / (client_count + 1).max(1);
|
let mut budget: u32 = BASE_BUDGET;
|
||||||
for entity in entities_by_player_proximity() {
|
for entity in entities_by_player_proximity() {
|
||||||
if budget == 0 { break; }
|
if budget == 0 { break; }
|
||||||
budget = budget.saturating_sub(entity.think(&mut world));
|
budget = budget.saturating_sub(entity.think(&mut world));
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Entity-vs-entity blocking: `step_allowed` only checks tiles. Once NPCs exist, occupied
|
||||||
|
tiles need to block too — in the sim *and* in the pathfinder's `blocked` closure.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 09 — Client camera *(camera follow done, entity interpolation open)*
|
### 09 — Client camera *(done — see Achieved)*
|
||||||
|
|
||||||
Sprite rendering already landed (see Achieved). Camera status:
|
Sprite rendering, camera follow and entity interpolation have all landed.
|
||||||
|
|
||||||
- [x] Smooth camera follow: the camera is a float pixel position (`cam` in `game.rs`)
|
|
||||||
that tracks the player linearly at ~64 px/s, snapping only to whole pixels at
|
|
||||||
render time (and outright on teleport-sized corrections). The viewport renders
|
|
||||||
with sub-tile offsets (31×31 tile pass + right-strip clip).
|
|
||||||
- [x] Entity interpolation: per-id lerp table (`EntityLerp` in `game.rs`) beside the
|
|
||||||
entity list — previous/current tile plus a clock, rendered as a pixel lerp over
|
|
||||||
one movement interval. Jumps of more than one tile (Chebyshev) snap. Purely
|
|
||||||
cosmetic; game logic keeps using authoritative tile positions.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Later — UI / HUD
|
### Later — UI / HUD
|
||||||
|
|
||||||
- Bitmap font renderer: 8×8 glyph sheet TGA, `draw_text(frame, font, x, y, color, text)` — see `notes/04`
|
- Bitmap font renderer: 8×8 glyph sheet PNG, `draw_text(frame, font, x, y, color, text)` — see `notes/04`
|
||||||
- UI primitives in `client/src/ui.rs`: `fill_rect`, `draw_rect`, `draw_hbar` — see `notes/05`
|
- UI primitives in `game/src/ui.rs`: `fill_rect`, `draw_rect`, `draw_hbar` — see `notes/05`
|
||||||
- HUD layout (320×240): HP bar + player name bottom 16px, message log right panel
|
- HUD layout (320×240): HP bar + player name bottom 16px, message log right panel
|
||||||
- A real clip-rect on the blit primitives belongs here — once the viewport and HUD panels are
|
- A real clip-rect on the blit primitives belongs here — once the viewport and HUD panels are
|
||||||
two distinct regions, the abstraction earns its keep. Not before.
|
two distinct regions, the abstraction earns its keep. Not before.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Later — Robustness + auth
|
### Later — Distribution
|
||||||
|
|
||||||
- Auth token handshake: replace source-address identity (`auth_token` field exists but is unused)
|
- Asset embedding: `include_bytes!` for single-binary distribution (assets are currently
|
||||||
- Per-address rate limiting for the action ingest (only meaningful once identity is real —
|
read relative to the working directory)
|
||||||
address spoofing bypasses any limit before then)
|
- Save / load: the world is a plain `Sim` value — serialize it. Roguelike convention is a
|
||||||
- Multi-datagram `EntityPacket`: server currently truncates at 66 entities (`net.rs` TODO)
|
single save slot deleted on load
|
||||||
- Asset embedding: `include_bytes!` for single-binary distribution
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -215,11 +144,8 @@ Sprite rendering already landed (see Achieved). Camera status:
|
|||||||
|
|
||||||
Once the simulation architecture is stable and a real level exists:
|
Once the simulation architecture is stable and a real level exists:
|
||||||
|
|
||||||
- Doors, interactive objects (server-authoritative state)
|
- Doors, interactive objects
|
||||||
- Combat: melee range check, HP, death, respawn
|
- Combat: melee range check, HP, death, permadeath
|
||||||
- Inventory: on-demand via `EntityQueryPacket` / `EntityDetailPacket`
|
- Inventory
|
||||||
- Day/night and weather on the slow tick — affect visibility, spawns
|
- Day/night and weather on the slow tick (`% 192`) — affect visibility, spawns
|
||||||
- Procedural dungeon generation (BSP or cellular automata feeding into the same tile format)
|
- Procedural dungeon generation (BSP or cellular automata feeding into the same tile format)
|
||||||
|
|
||||||
---
|
|
||||||
</content>
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "server"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
shared = { path = "../shared" }
|
|
||||||
bytemuck = "1.24.0"
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
|
|
||||||
pub struct TileFlags(pub u8);
|
|
||||||
|
|
||||||
impl TileFlags {
|
|
||||||
pub const COLLIDABLE: u8 = 1 << 0;
|
|
||||||
pub const OPAQUE: u8 = 1 << 1;
|
|
||||||
pub const LIQUID: u8 = 1 << 2;
|
|
||||||
|
|
||||||
pub fn collidable(self) -> bool { self.0 & Self::COLLIDABLE != 0 }
|
|
||||||
pub fn opaque(self) -> bool { self.0 & Self::OPAQUE != 0 }
|
|
||||||
pub fn liquid(self) -> bool { self.0 & Self::LIQUID != 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
|
|
||||||
pub struct TileDef { pub tile_id: u16, pub flags: TileFlags }
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PaletteFullError;
|
|
||||||
|
|
||||||
pub struct Chunk {
|
|
||||||
pub palette: [TileDef; 64],
|
|
||||||
pub pal_count: u8,
|
|
||||||
pub tiles: Box<[u8; 1024]>,
|
|
||||||
pub version: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Chunk {
|
|
||||||
/// Runtime use: all tiles empty, pal_count=1, version=1.
|
|
||||||
/// Clients with version=0 will always request this chunk.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
palette: [TileDef::default(); 64],
|
|
||||||
pal_count: 1,
|
|
||||||
tiles: Box::new([0u8; 1024]),
|
|
||||||
version: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find existing palette entry or insert a new one.
|
|
||||||
/// Index 0 is always the null sentinel; this short-circuits for default TileDef.
|
|
||||||
/// Returns (palette_index, was_new). Does NOT bump version.
|
|
||||||
fn find_or_insert_raw(&mut self, def: TileDef) -> Result<(u8, bool), PaletteFullError> {
|
|
||||||
if def == TileDef::default() {
|
|
||||||
return Ok((0, false));
|
|
||||||
}
|
|
||||||
for i in 1..self.pal_count as usize {
|
|
||||||
if self.palette[i] == def {
|
|
||||||
return Ok((i as u8, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.pal_count >= 64 {
|
|
||||||
return Err(PaletteFullError);
|
|
||||||
}
|
|
||||||
let idx = self.pal_count;
|
|
||||||
self.palette[idx as usize] = def;
|
|
||||||
self.pal_count += 1;
|
|
||||||
Ok((idx, true))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// World gen path. Calls the closure for every tile coordinate.
|
|
||||||
/// version=0 so the first StatePacket causes all clients to request it.
|
|
||||||
pub fn generate(f: impl Fn(u8, u8) -> TileDef) -> Result<Self, PaletteFullError> {
|
|
||||||
let mut chunk = Self {
|
|
||||||
palette: [TileDef::default(); 64],
|
|
||||||
pal_count: 1,
|
|
||||||
tiles: Box::new([0u8; 1024]),
|
|
||||||
version: 0,
|
|
||||||
};
|
|
||||||
for ly in 0u8..32 {
|
|
||||||
for lx in 0u8..32 {
|
|
||||||
let def = f(lx, ly);
|
|
||||||
let (idx, _) = chunk.find_or_insert_raw(def)?;
|
|
||||||
chunk.tiles[ly as usize * 32 + lx as usize] = idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chunk.version = 1;
|
|
||||||
Ok(chunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a tile at local coords. Bumps version if tile index or palette changed.
|
|
||||||
pub fn set_tile(&mut self, lx: u8, ly: u8, def: TileDef) -> Result<(), PaletteFullError> {
|
|
||||||
let (idx, was_new) = self.find_or_insert_raw(def)?;
|
|
||||||
let slot = &mut self.tiles[ly as usize * 32 + lx as usize];
|
|
||||||
let changed = *slot != idx;
|
|
||||||
*slot = idx;
|
|
||||||
if changed || was_new {
|
|
||||||
self.version = self.version.wrapping_add(1);
|
|
||||||
if self.version == 0 {
|
|
||||||
self.version = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_tile(&self, lx: u8, ly: u8) -> TileDef {
|
|
||||||
self.palette[self.tiles[ly as usize * 32 + lx as usize] as usize]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tile_flags(&self, lx: u8, ly: u8) -> TileFlags {
|
|
||||||
self.get_tile(lx, ly).flags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
mod chunk;
|
|
||||||
mod entity;
|
|
||||||
mod world;
|
|
||||||
mod sim;
|
|
||||||
mod net;
|
|
||||||
mod map;
|
|
||||||
|
|
||||||
use std::thread;
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
|
|
||||||
use chunk::{Chunk, TileDef, TileFlags};
|
|
||||||
use world::World;
|
|
||||||
use sim::Sim;
|
|
||||||
use net::Net;
|
|
||||||
use map::TileMap;
|
|
||||||
|
|
||||||
const TICK_HZ: u32 = 24;
|
|
||||||
const TICK_DURATION: Duration = Duration::from_micros(1_000_000 / TICK_HZ as u64);
|
|
||||||
|
|
||||||
/// Build a world from a loaded tile map. The map's top-left tile sits at world (0, 0); the
|
|
||||||
/// map is sliced into 32×32 chunks. Tiles inside a loaded chunk but outside the authored map
|
|
||||||
/// become an invisible solid border (id 0, collidable), so the walkable world edge sits flush
|
|
||||||
/// with the visible map rim instead of with the larger chunk boundary.
|
|
||||||
fn load_world(map: &TileMap) -> World {
|
|
||||||
let mut w = World::new();
|
|
||||||
let max_cx = (map.width.saturating_sub(1) >> 5) as i16;
|
|
||||||
let max_cy = (map.height.saturating_sub(1) >> 5) as i16;
|
|
||||||
for cy in 0..=max_cy {
|
|
||||||
for cx in 0..=max_cx {
|
|
||||||
let chunk = Chunk::generate(|lx, ly| {
|
|
||||||
let (wx, wy) = world::local_to_tile(cx, cy, lx, ly);
|
|
||||||
if wx < 0 || wy < 0 || wx >= map.width as i16 || wy >= map.height as i16 {
|
|
||||||
return TileDef { tile_id: 0, flags: TileFlags(TileFlags::COLLIDABLE) };
|
|
||||||
}
|
|
||||||
let id = map.tile(wx, wy);
|
|
||||||
TileDef { tile_id: id, flags: map::tile_flags(id) }
|
|
||||||
}).expect("palette overflow during world gen");
|
|
||||||
w.set_chunk(cx, cy, chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let map = TileMap::from_csv("assets/map_test");
|
|
||||||
println!("loaded map {}×{} ({} tiles)", map.width, map.height, map.tiles.len());
|
|
||||||
let world = load_world(&map);
|
|
||||||
let mut sim = Sim::new(world);
|
|
||||||
|
|
||||||
let mut net = Net::new("127.0.0.1:7777").expect("failed to bind UDP socket");
|
|
||||||
|
|
||||||
let mut next_tick = Instant::now() + TICK_DURATION;
|
|
||||||
let mut tick = 0u32;
|
|
||||||
loop {
|
|
||||||
net.drain_recv();
|
|
||||||
net.evict_stale();
|
|
||||||
|
|
||||||
for addr in net.drain_new_clients() {
|
|
||||||
let entity_id = sim.world.spawn_entity(0, (0, 0), 100);
|
|
||||||
net.add_client(addr, entity_id);
|
|
||||||
println!("client connected: {addr} → entity {entity_id}");
|
|
||||||
}
|
|
||||||
|
|
||||||
let actions = net.drain_actions();
|
|
||||||
sim.tick(tick, &actions);
|
|
||||||
net.broadcast(&sim.world, tick);
|
|
||||||
thread::sleep(next_tick.saturating_duration_since(Instant::now()));
|
|
||||||
next_tick += TICK_DURATION;
|
|
||||||
tick = tick.wrapping_add(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
use std::collections::{HashMap, VecDeque};
|
|
||||||
use std::mem::size_of;
|
|
||||||
use std::net::{SocketAddr, UdpSocket};
|
|
||||||
use std::sync::mpsc::{self, Receiver, SyncSender};
|
|
||||||
use std::thread;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use bytemuck::bytes_of;
|
|
||||||
use shared::{
|
|
||||||
ActionPacket, ChunkEntry, ChunkPacket, EntityEntry, EntityPacket,
|
|
||||||
Header, PingPacket, PongPacket, StatePacket,
|
|
||||||
MAGIC, VERSION, packet_type,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::world::World;
|
|
||||||
|
|
||||||
pub struct NetClient {
|
|
||||||
pub entity_id: u32,
|
|
||||||
pub last_seen: Instant,
|
|
||||||
pub send_phase: u8,
|
|
||||||
/// Tick-addressed actions received since the last drain, in arrival order —
|
|
||||||
/// `(target_tick, action)`. Window resolution, replacement and the lateness rule
|
|
||||||
/// all live in the sim; this is only the per-tick hand-off buffer, bounded so a
|
|
||||||
/// flood between drains cannot grow it.
|
|
||||||
pub pending_actions: VecDeque<(u32, u16)>,
|
|
||||||
pub last_cache: [ChunkEntry; 9],
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hand-off buffer bound. Drained every tick; an honest client sends at most one
|
|
||||||
/// action per movement window, so a handful is already generous.
|
|
||||||
const PENDING_ACTIONS_MAX: usize = 8;
|
|
||||||
|
|
||||||
pub struct Net {
|
|
||||||
clients: HashMap<SocketAddr, NetClient>,
|
|
||||||
new_addrs: Vec<SocketAddr>,
|
|
||||||
recv_rx: Receiver<(SocketAddr, Vec<u8>)>,
|
|
||||||
send_tx: SyncSender<(SocketAddr, Vec<u8>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Net {
|
|
||||||
pub fn new(addr: &str) -> std::io::Result<Self> {
|
|
||||||
let socket = UdpSocket::bind(addr)?;
|
|
||||||
|
|
||||||
let (recv_tx, recv_rx) = mpsc::sync_channel::<(SocketAddr, Vec<u8>)>(256);
|
|
||||||
let (send_tx, send_rx) = mpsc::sync_channel::<(SocketAddr, Vec<u8>)>(256);
|
|
||||||
|
|
||||||
let recv_sock = socket.try_clone()?;
|
|
||||||
thread::spawn(move || {
|
|
||||||
let mut buf = [0u8; 1200];
|
|
||||||
loop {
|
|
||||||
match recv_sock.recv_from(&mut buf) {
|
|
||||||
Ok((n, addr)) => {
|
|
||||||
if recv_tx.send((addr, buf[..n].to_vec())).is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let send_sock = socket.try_clone()?;
|
|
||||||
thread::spawn(move || {
|
|
||||||
loop {
|
|
||||||
match send_rx.recv() {
|
|
||||||
Ok((addr, data)) => { let _ = send_sock.send_to(&data, addr); }
|
|
||||||
Err(_) => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// socket is dropped here; send/recv threads own their copies
|
|
||||||
drop(socket);
|
|
||||||
|
|
||||||
Ok(Net { clients: HashMap::new(), new_addrs: Vec::new(), recv_rx, send_tx })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_client(&mut self, addr: SocketAddr, entity_id: u32) {
|
|
||||||
self.clients.insert(addr, NetClient {
|
|
||||||
entity_id,
|
|
||||||
last_seen: Instant::now(),
|
|
||||||
send_phase: (entity_id % 2) as u8,
|
|
||||||
pending_actions: VecDeque::new(),
|
|
||||||
last_cache: [ChunkEntry { chunk_id: 0, version: 0 }; 9],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drain_recv(&mut self) {
|
|
||||||
loop {
|
|
||||||
match self.recv_rx.try_recv() {
|
|
||||||
Ok((addr, data)) => self.handle_packet(addr, data),
|
|
||||||
Err(_) => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_packet(&mut self, addr: SocketAddr, data: Vec<u8>) {
|
|
||||||
if data.len() < size_of::<Header>() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let header: Header = bytemuck::pod_read_unaligned(&data[..size_of::<Header>()]);
|
|
||||||
if header.magic != MAGIC || header.version != VERSION {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if header.packet_type == packet_type::ACTION && data.len() == size_of::<ActionPacket>() {
|
|
||||||
let pkt: ActionPacket = bytemuck::pod_read_unaligned(&data);
|
|
||||||
if let Some(client) = self.clients.get_mut(&addr) {
|
|
||||||
client.last_seen = Instant::now();
|
|
||||||
client.last_cache = pkt.cache;
|
|
||||||
// `target_tick == 0` marks pure keep-alive / cache-ack packets — they
|
|
||||||
// must never enter the schedule. Everything else (including NOOPs, which
|
|
||||||
// *retract* the addressed window) is handed to the sim in arrival order.
|
|
||||||
let target_tick = pkt.target_tick;
|
|
||||||
if target_tick != 0 && client.pending_actions.len() < PENDING_ACTIONS_MAX {
|
|
||||||
client.pending_actions.push_back((target_tick, pkt.player_action));
|
|
||||||
}
|
|
||||||
} else if !self.new_addrs.contains(&addr) {
|
|
||||||
self.new_addrs.push(addr);
|
|
||||||
}
|
|
||||||
} else if header.packet_type == packet_type::PING && data.len() == size_of::<PingPacket>() {
|
|
||||||
let pkt: PingPacket = bytemuck::pod_read_unaligned(&data);
|
|
||||||
let pong = PongPacket {
|
|
||||||
header: Header::new(packet_type::PONG),
|
|
||||||
timestamp_ms: pkt.timestamp_ms,
|
|
||||||
};
|
|
||||||
let _ = self.send_tx.send((addr, bytes_of(&pong).to_vec()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drain_new_clients(&mut self) -> Vec<SocketAddr> {
|
|
||||||
std::mem::take(&mut self.new_addrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn evict_stale(&mut self) {
|
|
||||||
self.clients.retain(|_, c| c.last_seen.elapsed().as_secs() < 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drain_actions(&mut self) -> Vec<(u32, u32, u16)> {
|
|
||||||
self.clients.values_mut()
|
|
||||||
.flat_map(|c| {
|
|
||||||
let entity_id = c.entity_id;
|
|
||||||
c.pending_actions.drain(..)
|
|
||||||
.map(move |(target_tick, action)| (entity_id, target_tick, action))
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn broadcast(&self, world: &World, tick: u32) {
|
|
||||||
for (addr, client) in &self.clients {
|
|
||||||
if tick % 2 != client.send_phase as u32 { continue; }
|
|
||||||
|
|
||||||
// Step 1 — resolve player position
|
|
||||||
let Some(entity) = world.entities.get(&client.entity_id) else { continue };
|
|
||||||
let pos = entity.pos;
|
|
||||||
let (player_cx, player_cy) = crate::world::tile_to_chunk(pos.0, pos.1);
|
|
||||||
|
|
||||||
// Step 2 — build 9-slot chunk manifest
|
|
||||||
let mut chunks = [ChunkEntry { chunk_id: 0, version: 0 }; 9];
|
|
||||||
for dy in -1i16..=1 {
|
|
||||||
for dx in -1i16..=1 {
|
|
||||||
let slot = ((dy + 1) * 3 + (dx + 1)) as usize;
|
|
||||||
let cx = player_cx + dx;
|
|
||||||
let cy = player_cy + dy;
|
|
||||||
chunks[slot] = ChunkEntry {
|
|
||||||
chunk_id: shared::chunk_id(cx, cy),
|
|
||||||
version: world.chunk(cx, cy).map_or(0, |c| c.version),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3 — build EntityEntry list + checksum
|
|
||||||
let viewport_entities = world.entities_in_viewport(pos);
|
|
||||||
let mut entries: Vec<EntityEntry> = viewport_entities.iter().map(|e| EntityEntry {
|
|
||||||
id: e.id,
|
|
||||||
type_id: e.type_id,
|
|
||||||
pos_x: e.pos.0,
|
|
||||||
pos_y: e.pos.1,
|
|
||||||
hp: e.hp,
|
|
||||||
hp_max: e.hp_max,
|
|
||||||
elo: 0,
|
|
||||||
entity_flags: 0,
|
|
||||||
meta_len: 0,
|
|
||||||
}).collect();
|
|
||||||
if entries.len() > 66 {
|
|
||||||
entries.truncate(66); // TODO: multi-datagram
|
|
||||||
}
|
|
||||||
let checksum = fnv1a_entities(&entries);
|
|
||||||
|
|
||||||
// Step 4 — send StatePacket (72 bytes)
|
|
||||||
let state = StatePacket {
|
|
||||||
header: Header::new(packet_type::STATE),
|
|
||||||
tick,
|
|
||||||
chunks,
|
|
||||||
player_entity_id: client.entity_id,
|
|
||||||
entity_checksum: checksum,
|
|
||||||
};
|
|
||||||
let _ = self.send_tx.send((*addr, bytes_of(&state).to_vec()));
|
|
||||||
|
|
||||||
// Step 5 — send EntityPacket (1200 bytes)
|
|
||||||
let mut pkt = <EntityPacket as bytemuck::Zeroable>::zeroed();
|
|
||||||
pkt.header = Header::new(packet_type::ENTITY);
|
|
||||||
pkt.tick = tick;
|
|
||||||
pkt.entity_count = entries.len() as u8;
|
|
||||||
pkt.packet_flags = 0; // no more-flag; single datagram
|
|
||||||
for (i, e) in entries.iter().enumerate() { pkt.entities[i] = *e; }
|
|
||||||
let _ = self.send_tx.send((*addr, bytes_of(&pkt).to_vec()));
|
|
||||||
|
|
||||||
// Step 6 — send ChunkPackets for cache misses
|
|
||||||
for slot in 0..9 {
|
|
||||||
let current = chunks[slot];
|
|
||||||
let cached = client.last_cache[slot];
|
|
||||||
let miss = cached.chunk_id != current.chunk_id || cached.version != current.version;
|
|
||||||
if miss {
|
|
||||||
let (cx, cy) = shared::chunk_coords(current.chunk_id);
|
|
||||||
if let Some(chunk) = world.chunk(cx, cy) {
|
|
||||||
let mut cpkt = <ChunkPacket as bytemuck::Zeroable>::zeroed();
|
|
||||||
cpkt.header = Header::new(packet_type::CHUNK);
|
|
||||||
cpkt.chunk = current;
|
|
||||||
cpkt.pal_count = chunk.pal_count;
|
|
||||||
for i in 0..64 { cpkt.palette[i] = chunk.palette[i].tile_id; }
|
|
||||||
cpkt.tiles = pack_tiles(&chunk.tiles);
|
|
||||||
let _ = self.send_tx.send((*addr, bytes_of(&cpkt).to_vec()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fnv1a_entities(entries: &[EntityEntry]) -> u32 {
|
|
||||||
const FNV_BASIS: u32 = 2_166_136_261;
|
|
||||||
const FNV_PRIME: u32 = 16_777_619;
|
|
||||||
let mut h = FNV_BASIS;
|
|
||||||
for e in entries {
|
|
||||||
for &byte in bytes_of(e) {
|
|
||||||
h ^= byte as u32;
|
|
||||||
h = h.wrapping_mul(FNV_PRIME);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pack_tiles(tiles: &[u8; 1024]) -> [u8; 768] {
|
|
||||||
let mut raw = [0u8; 768];
|
|
||||||
for i in 0..256 {
|
|
||||||
let ia = tiles[i*4] & 0x3F;
|
|
||||||
let ib = tiles[i*4+1] & 0x3F;
|
|
||||||
let ic = tiles[i*4+2] & 0x3F;
|
|
||||||
let id = tiles[i*4+3] & 0x3F;
|
|
||||||
raw[i*3] = ia | (ib << 6);
|
|
||||||
raw[i*3+1] = (ib >> 2) | (ic << 4);
|
|
||||||
raw[i*3+2] = (ic >> 4) | (id << 2);
|
|
||||||
}
|
|
||||||
raw
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
use std::collections::{BTreeMap, HashMap};
|
|
||||||
use shared::{ACTION_WINDOW_HORIZON, TICKS_PER_MOVE};
|
|
||||||
use crate::world::World;
|
|
||||||
|
|
||||||
pub struct Sim {
|
|
||||||
pub world: World,
|
|
||||||
/// Scheduled actions per entity, keyed by movement window (`tick / TICKS_PER_MOVE`).
|
|
||||||
/// The window slot is the whole ordering model: an action addressed to an occupied
|
|
||||||
/// window *replaces* it (retraction and rescheduling by the client, dedup of
|
|
||||||
/// retransmits), a late action only fills the next window if it is empty, and
|
|
||||||
/// anything past `ACTION_WINDOW_HORIZON` is dropped — so no client can grow server
|
|
||||||
/// memory or move faster than one action per window.
|
|
||||||
pending: HashMap<u32, BTreeMap<u32, u16>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sim {
|
|
||||||
pub fn new(world: World) -> Self {
|
|
||||||
Self { world, pending: HashMap::new() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tick(&mut self, tick: u32, actions: &[(u32, u32, u16)]) {
|
|
||||||
// The earliest window still addressable at this tick. On a movement tick that is
|
|
||||||
// the window executing *this call* — actions arriving the same tick still count.
|
|
||||||
let min_window = tick.div_ceil(TICKS_PER_MOVE);
|
|
||||||
|
|
||||||
for &(entity_id, target_tick, action) in actions {
|
|
||||||
let slots = self.pending.entry(entity_id).or_default();
|
|
||||||
let window = target_tick.div_ceil(TICKS_PER_MOVE);
|
|
||||||
if window < min_window {
|
|
||||||
// Late. A movement action keeps its *order* instead of its time: it
|
|
||||||
// fills the first still-empty upcoming window, so bunched late arrivals
|
|
||||||
// don't collapse onto one slot and eat each other. A late NOOP is
|
|
||||||
// dropped — once its window has passed, its cancellation intent is
|
|
||||||
// ambiguous, and as a gap-filler it would block real steps (worst case
|
|
||||||
// one stale step executes; the client's reconciliation handles that).
|
|
||||||
if shared::action_delta(action).is_some() {
|
|
||||||
let gap = (min_window..min_window + ACTION_WINDOW_HORIZON as u32)
|
|
||||||
.find(|w| !slots.contains_key(w));
|
|
||||||
if let Some(w) = gap {
|
|
||||||
slots.insert(w, action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if window - min_window < ACTION_WINDOW_HORIZON as u32 {
|
|
||||||
slots.insert(window, action); // newest addressing wins
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tick.is_multiple_of(TICKS_PER_MOVE) {
|
|
||||||
self.entity_tick(tick / TICKS_PER_MOVE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn entity_tick(&mut self, window: u32) {
|
|
||||||
// Exactly one action per entity per movement window, then the window is gone.
|
|
||||||
let actions: Vec<(u32, u16)> = self.pending.iter_mut()
|
|
||||||
.filter_map(|(&id, slots)| {
|
|
||||||
let action = slots.remove(&window);
|
|
||||||
slots.retain(|&w, _| w > window); // drop anything the timeline passed by
|
|
||||||
action.map(|a| (id, a))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
self.pending.retain(|_, slots| !slots.is_empty());
|
|
||||||
|
|
||||||
for (entity_id, action) in actions {
|
|
||||||
let delta = match shared::action_delta(action) {
|
|
||||||
Some(d) => d,
|
|
||||||
None => continue, // NOOP or garbage
|
|
||||||
};
|
|
||||||
let pos = match self.world.entities.get(&entity_id) {
|
|
||||||
Some(e) => e.pos,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
let from = (pos.0 as i32, pos.1 as i32);
|
|
||||||
let to = (from.0 + delta.0, from.1 + delta.1);
|
|
||||||
// `step_allowed` is the shared movement rule (chessboard geometry, no corner
|
|
||||||
// cutting) — the client predicts with the exact same function.
|
|
||||||
let allowed = shared::step_allowed(from, to, |x, y| {
|
|
||||||
self.world.tile_flags(x as i16, y as i16).map_or(true, |f| f.collidable())
|
|
||||||
});
|
|
||||||
if allowed {
|
|
||||||
self.world.move_entity(entity_id, (to.0 as i16, to.1 as i16));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "shared"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bytemuck = { version = "1.24.0", features = ["derive", "min_const_generics"] }
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
use bytemuck::{Pod, Zeroable};
|
|
||||||
|
|
||||||
pub mod player_action {
|
|
||||||
pub const NOOP: u16 = 0;
|
|
||||||
pub const NORTH: u16 = 1;
|
|
||||||
pub const EAST: u16 = 2;
|
|
||||||
pub const SOUTH: u16 = 3;
|
|
||||||
pub const WEST: u16 = 4;
|
|
||||||
pub const NORTH_EAST: u16 = 5;
|
|
||||||
pub const SOUTH_EAST: u16 = 6;
|
|
||||||
pub const SOUTH_WEST: u16 = 7;
|
|
||||||
pub const NORTH_WEST: u16 = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The movement delta of an action, or `None` for `NOOP` and unknown values.
|
|
||||||
pub fn action_delta(action: u16) -> Option<(i32, i32)> {
|
|
||||||
match action {
|
|
||||||
player_action::NORTH => Some((0, -1)),
|
|
||||||
player_action::EAST => Some((1, 0)),
|
|
||||||
player_action::SOUTH => Some((0, 1)),
|
|
||||||
player_action::WEST => Some((-1, 0)),
|
|
||||||
player_action::NORTH_EAST => Some((1, -1)),
|
|
||||||
player_action::SOUTH_EAST => Some((1, 1)),
|
|
||||||
player_action::SOUTH_WEST => Some((-1, 1)),
|
|
||||||
player_action::NORTH_WEST => Some((-1, -1)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The action for a single-step delta. Inverse of [`action_delta`]; panics on anything
|
|
||||||
/// that is not a king move.
|
|
||||||
pub fn delta_action(dx: i32, dy: i32) -> u16 {
|
|
||||||
match (dx, dy) {
|
|
||||||
(0, -1) => player_action::NORTH,
|
|
||||||
(1, 0) => player_action::EAST,
|
|
||||||
(0, 1) => player_action::SOUTH,
|
|
||||||
(-1, 0) => player_action::WEST,
|
|
||||||
(1, -1) => player_action::NORTH_EAST,
|
|
||||||
(1, 1) => player_action::SOUTH_EAST,
|
|
||||||
(-1, 1) => player_action::SOUTH_WEST,
|
|
||||||
(-1, -1) => player_action::NORTH_WEST,
|
|
||||||
d => panic!("non-step delta {d:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The single-step movement rule, shared by the server sim, client prediction and the
|
|
||||||
/// client pathfinder so all three agree on the same physics.
|
|
||||||
///
|
|
||||||
/// World geometry is chessboard (Chebyshev): diagonal and cardinal steps are the same
|
|
||||||
/// distance, so a step is any king move onto a free tile. A diagonal step additionally
|
|
||||||
/// requires *both* orthogonal neighbor tiles to be free — no squeezing between two
|
|
||||||
/// diagonally touching blockers (corner cutting).
|
|
||||||
pub fn step_allowed(
|
|
||||||
from: (i32, i32),
|
|
||||||
to: (i32, i32),
|
|
||||||
blocked: impl Fn(i32, i32) -> bool,
|
|
||||||
) -> bool {
|
|
||||||
let (dx, dy) = (to.0 - from.0, to.1 - from.1);
|
|
||||||
if dx.abs() > 1 || dy.abs() > 1 || (dx == 0 && dy == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if blocked(to.0, to.1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
dx == 0 || dy == 0 || (!blocked(from.0 + dx, from.1) && !blocked(from.0, from.1 + dy))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Server base tick rate. The shared timeline all action scheduling is expressed in.
|
|
||||||
pub const TICK_HZ: u32 = 24;
|
|
||||||
|
|
||||||
/// Movement resolves on every `TICKS_PER_MOVE`-th tick — one "movement window" per
|
|
||||||
/// `TICKS_PER_MOVE` ticks (6 Hz). Window `w` executes at tick `w * TICKS_PER_MOVE`.
|
|
||||||
pub const TICKS_PER_MOVE: u32 = 4;
|
|
||||||
|
|
||||||
/// How many *future* movement windows a client may address. Part of the protocol
|
|
||||||
/// contract: actions targeted beyond this horizon are dropped, and the client's
|
|
||||||
/// unconfirmed in-flight steps must stay within it.
|
|
||||||
pub const ACTION_WINDOW_HORIZON: usize = 3;
|
|
||||||
|
|
||||||
pub const MAGIC: u16 = 0x524C;
|
|
||||||
pub const VERSION: u8 = 1;
|
|
||||||
|
|
||||||
pub mod packet_type {
|
|
||||||
pub const STATE: u8 = 0;
|
|
||||||
pub const ACTION: u8 = 1;
|
|
||||||
pub const CHUNK: u8 = 2;
|
|
||||||
pub const ENTITY: u8 = 3;
|
|
||||||
pub const ENTITY_QUERY: u8 = 4;
|
|
||||||
pub const ENTITY_DETAIL: u8 = 5;
|
|
||||||
pub const PING: u8 = 6;
|
|
||||||
pub const PONG: u8 = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encodes signed chunk grid coordinates into the wire chunk_id format.
|
|
||||||
pub fn chunk_id(cx: i16, cy: i16) -> u32 {
|
|
||||||
(cx as u16 as u32) | ((cy as u16 as u32) << 16)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decodes a wire chunk_id back to signed chunk grid coordinates.
|
|
||||||
pub fn chunk_coords(id: u32) -> (i16, i16) {
|
|
||||||
(id as u16 as i16, (id >> 16) as u16 as i16)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shared movement vocabulary: whether a tile id blocks movement.
|
|
||||||
///
|
|
||||||
/// Single source of truth for both the server (collision in the sim) and the client
|
|
||||||
/// (movement prediction). Keep this in lockstep with the tileset in `overworld.tga`.
|
|
||||||
///
|
|
||||||
/// Test values for now — a proper tile-data file format replaces this table later.
|
|
||||||
pub fn tile_collidable(tile_id: u16) -> bool {
|
|
||||||
match tile_id {
|
|
||||||
0 => true, // empty / world border (the server pads chunks past the map rim with id 0)
|
|
||||||
146 => true, // trees / rocks
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct Header {
|
|
||||||
pub magic: u16,
|
|
||||||
pub version: u8,
|
|
||||||
pub packet_type: u8,
|
|
||||||
pub client_type: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Header {
|
|
||||||
pub fn new(packet_type: u8) -> Self {
|
|
||||||
Self { magic: MAGIC, version: VERSION, packet_type, client_type: 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct ChunkEntry {
|
|
||||||
pub chunk_id: u32,
|
|
||||||
pub version: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct ActionPacket {
|
|
||||||
pub header: Header,
|
|
||||||
pub auth_token: u64,
|
|
||||||
/// The server tick this action is scheduled for: it executes in the movement window
|
|
||||||
/// covering that tick, or — if it arrives late — in the next window *if that slot is
|
|
||||||
/// still empty* (late actions fill gaps, they never override newer intent). A second
|
|
||||||
/// action addressed to the same window replaces the first, so a scheduled step can be
|
|
||||||
/// retracted (NOOP) or changed until its window executes. `0` means "no scheduling
|
|
||||||
/// intent": pure keep-alive / cache-ack packets that must never touch the queue.
|
|
||||||
pub target_tick: u32,
|
|
||||||
/// Slot index = (dy+1)*3 + (dx+1), dx/dy ∈ {-1, 0, 1}.
|
|
||||||
/// Slot 4 is always the player's current chunk.
|
|
||||||
pub cache: [ChunkEntry; 9],
|
|
||||||
pub player_action: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
const _: () = assert!(std::mem::size_of::<ChunkEntry>() == 6);
|
|
||||||
const _: () = assert!(std::mem::size_of::<ActionPacket>() == 74);
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct StatePacket {
|
|
||||||
pub header: Header, // 6 (offset 0)
|
|
||||||
pub tick: u32, // 4 (offset 6)
|
|
||||||
/// Slot index = (dy+1)*3 + (dx+1), dx/dy ∈ {-1, 0, 1}.
|
|
||||||
/// Slot 4 is always the player's current chunk.
|
|
||||||
pub chunks: [ChunkEntry; 9], // 54 (offset 10)
|
|
||||||
pub player_entity_id: u32, // 4 (offset 64)
|
|
||||||
pub entity_checksum: u32, // 4 (offset 68)
|
|
||||||
} // total: 72 bytes
|
|
||||||
|
|
||||||
const _: () = assert!(std::mem::size_of::<StatePacket>() == 72);
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct PingPacket {
|
|
||||||
pub header: Header,
|
|
||||||
pub timestamp_ms: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct PongPacket {
|
|
||||||
pub header: Header,
|
|
||||||
pub timestamp_ms: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct ChunkPacket {
|
|
||||||
pub header: Header, // 6 bytes
|
|
||||||
pub chunk: ChunkEntry, // 6 bytes
|
|
||||||
pub pal_count: u8, // 1 byte
|
|
||||||
pub palette: [u16; 64], // 128 bytes
|
|
||||||
pub tiles: [u8; 768], // 768 bytes — 1024 tiles, 6-bit packed
|
|
||||||
} // total: 909 bytes
|
|
||||||
|
|
||||||
const _: () = assert!(std::mem::size_of::<ChunkPacket>() == 909);
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
||||||
pub struct EntityEntry {
|
|
||||||
pub id: u32,
|
|
||||||
pub type_id: u16,
|
|
||||||
pub pos_x: i16,
|
|
||||||
pub pos_y: i16,
|
|
||||||
pub hp: u16,
|
|
||||||
pub hp_max: u16,
|
|
||||||
pub elo: u16,
|
|
||||||
pub entity_flags: u8,
|
|
||||||
pub meta_len: u8,
|
|
||||||
}
|
|
||||||
const _: () = assert!(std::mem::size_of::<EntityEntry>() == 18);
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
||||||
pub struct EntityPacket {
|
|
||||||
pub header: Header,
|
|
||||||
pub tick: u32,
|
|
||||||
pub entity_count: u8,
|
|
||||||
pub packet_flags: u8,
|
|
||||||
pub entities: [EntityEntry; 66],
|
|
||||||
}
|
|
||||||
const _: () = assert!(std::mem::size_of::<EntityPacket>() == 1200);
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "netsim"
|
name = "sim"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
Executable
+53
@@ -0,0 +1,53 @@
|
|||||||
|
/// Gameplay properties of a tile, derived from its id (see `map::tile_flags`).
|
||||||
|
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
|
||||||
|
pub struct TileFlags(pub u8);
|
||||||
|
|
||||||
|
impl TileFlags {
|
||||||
|
pub const COLLIDABLE: u8 = 1 << 0;
|
||||||
|
pub const OPAQUE: u8 = 1 << 1;
|
||||||
|
pub const LIQUID: u8 = 1 << 2;
|
||||||
|
|
||||||
|
pub fn collidable(self) -> bool { self.0 & Self::COLLIDABLE != 0 }
|
||||||
|
pub fn opaque(self) -> bool { self.0 & Self::OPAQUE != 0 }
|
||||||
|
pub fn liquid(self) -> bool { self.0 & Self::LIQUID != 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const CHUNK_SIZE: usize = 32;
|
||||||
|
pub const CHUNK_TILES: usize = CHUNK_SIZE * CHUNK_SIZE;
|
||||||
|
|
||||||
|
/// A 32×32 block of tile ids, row-major. This is the whole chunk: flags are derived from
|
||||||
|
/// the id on lookup, so the in-memory layout is already the plain `u16` array a binary
|
||||||
|
/// on-disk format would store.
|
||||||
|
pub struct Chunk {
|
||||||
|
pub tiles: Box<[u16; CHUNK_TILES]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chunk {
|
||||||
|
/// All tiles id 0 (empty / solid border).
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { tiles: Box::new([0; CHUNK_TILES]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// World gen path. Calls the closure for every local tile coordinate.
|
||||||
|
pub fn generate(f: impl Fn(u8, u8) -> u16) -> Self {
|
||||||
|
let mut chunk = Self::new();
|
||||||
|
for ly in 0..CHUNK_SIZE {
|
||||||
|
for lx in 0..CHUNK_SIZE {
|
||||||
|
chunk.tiles[ly * CHUNK_SIZE + lx] = f(lx as u8, ly as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_tile(&mut self, lx: u8, ly: u8, id: u16) {
|
||||||
|
self.tiles[ly as usize * CHUNK_SIZE + lx as usize] = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tile(&self, lx: u8, ly: u8) -> u16 {
|
||||||
|
self.tiles[ly as usize * CHUNK_SIZE + lx as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tile_flags(&self, lx: u8, ly: u8) -> TileFlags {
|
||||||
|
crate::map::tile_flags(self.get_tile(lx, ly))
|
||||||
|
}
|
||||||
|
}
|
||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
//! Headless world simulation: tiles, entities, movement rules and the tick loop.
|
||||||
|
//! No platform or rendering dependencies — the game binary drives `Sim::tick`.
|
||||||
|
|
||||||
|
pub mod chunk;
|
||||||
|
pub mod entity;
|
||||||
|
pub mod map;
|
||||||
|
pub mod world;
|
||||||
|
mod sim;
|
||||||
|
|
||||||
|
pub use sim::Sim;
|
||||||
|
|
||||||
|
use chunk::Chunk;
|
||||||
|
use map::TileMap;
|
||||||
|
use world::World;
|
||||||
|
|
||||||
|
pub mod player_action {
|
||||||
|
pub const NOOP: u16 = 0;
|
||||||
|
pub const NORTH: u16 = 1;
|
||||||
|
pub const EAST: u16 = 2;
|
||||||
|
pub const SOUTH: u16 = 3;
|
||||||
|
pub const WEST: u16 = 4;
|
||||||
|
pub const NORTH_EAST: u16 = 5;
|
||||||
|
pub const SOUTH_EAST: u16 = 6;
|
||||||
|
pub const SOUTH_WEST: u16 = 7;
|
||||||
|
pub const NORTH_WEST: u16 = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The movement delta of an action, or `None` for `NOOP` and unknown values.
|
||||||
|
pub fn action_delta(action: u16) -> Option<(i32, i32)> {
|
||||||
|
match action {
|
||||||
|
player_action::NORTH => Some((0, -1)),
|
||||||
|
player_action::EAST => Some((1, 0)),
|
||||||
|
player_action::SOUTH => Some((0, 1)),
|
||||||
|
player_action::WEST => Some((-1, 0)),
|
||||||
|
player_action::NORTH_EAST => Some((1, -1)),
|
||||||
|
player_action::SOUTH_EAST => Some((1, 1)),
|
||||||
|
player_action::SOUTH_WEST => Some((-1, 1)),
|
||||||
|
player_action::NORTH_WEST => Some((-1, -1)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The action for a single-step delta. Inverse of [`action_delta`]; panics on anything
|
||||||
|
/// that is not a king move.
|
||||||
|
pub fn delta_action(dx: i32, dy: i32) -> u16 {
|
||||||
|
match (dx, dy) {
|
||||||
|
(0, -1) => player_action::NORTH,
|
||||||
|
(1, 0) => player_action::EAST,
|
||||||
|
(0, 1) => player_action::SOUTH,
|
||||||
|
(-1, 0) => player_action::WEST,
|
||||||
|
(1, -1) => player_action::NORTH_EAST,
|
||||||
|
(1, 1) => player_action::SOUTH_EAST,
|
||||||
|
(-1, 1) => player_action::SOUTH_WEST,
|
||||||
|
(-1, -1) => player_action::NORTH_WEST,
|
||||||
|
d => panic!("non-step delta {d:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The single-step movement rule, shared by the sim and the pathfinder so both agree
|
||||||
|
/// on the same physics.
|
||||||
|
///
|
||||||
|
/// World geometry is chessboard (Chebyshev): diagonal and cardinal steps are the same
|
||||||
|
/// distance, so a step is any king move onto a free tile. A diagonal step additionally
|
||||||
|
/// requires *both* orthogonal neighbor tiles to be free — no squeezing between two
|
||||||
|
/// diagonally touching blockers (corner cutting).
|
||||||
|
pub fn step_allowed(
|
||||||
|
from: (i32, i32),
|
||||||
|
to: (i32, i32),
|
||||||
|
blocked: impl Fn(i32, i32) -> bool,
|
||||||
|
) -> bool {
|
||||||
|
let (dx, dy) = (to.0 - from.0, to.1 - from.1);
|
||||||
|
if dx.abs() > 1 || dy.abs() > 1 || (dx == 0 && dy == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if blocked(to.0, to.1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dx == 0 || dy == 0 || (!blocked(from.0 + dx, from.1) && !blocked(from.0, from.1 + dy))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Base tick rate. The timeline all action scheduling is expressed in.
|
||||||
|
pub const TICK_HZ: u32 = 24;
|
||||||
|
|
||||||
|
/// Movement resolves on every `TICKS_PER_MOVE`-th tick — one "movement window" per
|
||||||
|
/// `TICKS_PER_MOVE` ticks (6 Hz). Window `w` executes at tick `w * TICKS_PER_MOVE`.
|
||||||
|
pub const TICKS_PER_MOVE: u32 = 4;
|
||||||
|
|
||||||
|
/// Movement vocabulary: whether a tile id blocks movement. Keep this in lockstep with
|
||||||
|
/// the tileset in `overworld.tga`.
|
||||||
|
///
|
||||||
|
/// Test values for now — a proper tile-data file format replaces this table later.
|
||||||
|
pub fn tile_collidable(tile_id: u16) -> bool {
|
||||||
|
match tile_id {
|
||||||
|
0 => true, // empty / world border (chunks past the map rim are padded with id 0)
|
||||||
|
146 => true, // trees / rocks
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a world from a loaded tile map. The map's top-left tile sits at world (0, 0); the
|
||||||
|
/// map is sliced into 32×32 chunks. Tiles inside a loaded chunk but outside the authored map
|
||||||
|
/// get id 0 (solid, see `tile_collidable`), so the walkable world edge sits flush with the
|
||||||
|
/// visible map rim instead of with the larger chunk boundary.
|
||||||
|
pub fn load_world(map: &TileMap) -> World {
|
||||||
|
let mut w = World::new();
|
||||||
|
let max_cx = (map.width.saturating_sub(1) >> 5) as i16;
|
||||||
|
let max_cy = (map.height.saturating_sub(1) >> 5) as i16;
|
||||||
|
for cy in 0..=max_cy {
|
||||||
|
for cx in 0..=max_cx {
|
||||||
|
let chunk = Chunk::generate(|lx, ly| {
|
||||||
|
let (wx, wy) = world::local_to_tile(cx, cy, lx, ly);
|
||||||
|
map.tile(wx, wy) // 0 outside the map
|
||||||
|
});
|
||||||
|
w.set_chunk(cx, cy, chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w
|
||||||
|
}
|
||||||
@@ -64,12 +64,12 @@ impl TileMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gameplay flags for a global tile id. Collision comes from the shared movement
|
/// Gameplay flags for a global tile id. Collision comes from the movement vocabulary
|
||||||
/// vocabulary (`shared::tile_collidable`) so the client can predict it identically;
|
/// (`crate::tile_collidable`) so the pathfinder predicts it identically; other flags
|
||||||
/// other flags (opaque, …) stay server-side. Tile ids index into `overworld.tga`.
|
/// (opaque, …) will join here. Tile ids index into `overworld.png`.
|
||||||
pub fn tile_flags(tile_id: u16) -> TileFlags {
|
pub fn tile_flags(tile_id: u16) -> TileFlags {
|
||||||
let mut bits = 0u8;
|
let mut bits = 0u8;
|
||||||
if shared::tile_collidable(tile_id) {
|
if crate::tile_collidable(tile_id) {
|
||||||
bits |= TileFlags::COLLIDABLE;
|
bits |= TileFlags::COLLIDABLE;
|
||||||
}
|
}
|
||||||
TileFlags(bits)
|
TileFlags(bits)
|
||||||
Executable
+126
@@ -0,0 +1,126 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::TICKS_PER_MOVE;
|
||||||
|
use crate::world::World;
|
||||||
|
|
||||||
|
pub struct Sim {
|
||||||
|
pub world: World,
|
||||||
|
/// Base tick counter (24 Hz). Movement resolves every `TICKS_PER_MOVE`-th tick.
|
||||||
|
pub tick: u32,
|
||||||
|
/// Each entity's intent for the next movement window. Setting it again replaces
|
||||||
|
/// it, executing the window consumes it — so an actor moves at most one tile per
|
||||||
|
/// window no matter how often it changes its mind in between.
|
||||||
|
intents: HashMap<u32, u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sim {
|
||||||
|
pub fn new(world: World) -> Self {
|
||||||
|
Self { world, tick: 0, intents: HashMap::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Schedule `action` for `entity_id`'s next movement window. NOOP clears the intent.
|
||||||
|
pub fn set_action(&mut self, entity_id: u32, action: u16) {
|
||||||
|
if crate::action_delta(action).is_some() {
|
||||||
|
self.intents.insert(entity_id, action);
|
||||||
|
} else {
|
||||||
|
self.intents.remove(&entity_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_action(&mut self, entity_id: u32) {
|
||||||
|
self.intents.remove(&entity_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance the world by one base tick. Returns `true` when this tick executed a
|
||||||
|
/// movement window, i.e. entity positions may have changed.
|
||||||
|
pub fn step(&mut self) -> bool {
|
||||||
|
self.tick = self.tick.wrapping_add(1);
|
||||||
|
if self.tick.is_multiple_of(TICKS_PER_MOVE) {
|
||||||
|
self.entity_tick();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entity_tick(&mut self) {
|
||||||
|
for (entity_id, action) in self.intents.drain() {
|
||||||
|
let delta = match crate::action_delta(action) {
|
||||||
|
Some(d) => d,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
let pos = match self.world.entities.get(&entity_id) {
|
||||||
|
Some(e) => e.pos,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
let from = (pos.0 as i32, pos.1 as i32);
|
||||||
|
let to = (from.0 + delta.0, from.1 + delta.1);
|
||||||
|
// `step_allowed` is the shared movement rule (chessboard geometry, no corner
|
||||||
|
// cutting) — the pathfinder plans with the exact same function.
|
||||||
|
let allowed = crate::step_allowed(from, to, |x, y| {
|
||||||
|
self.world.tile_flags(x as i16, y as i16).map_or(true, |f| f.collidable())
|
||||||
|
});
|
||||||
|
if allowed {
|
||||||
|
self.world.move_entity(entity_id, (to.0 as i16, to.1 as i16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::chunk::Chunk;
|
||||||
|
use crate::player_action;
|
||||||
|
|
||||||
|
/// Open floor (id 1) with a single wall tile at (5, 4) — id 146 is collidable in
|
||||||
|
/// the tile vocabulary.
|
||||||
|
fn open_world() -> World {
|
||||||
|
let mut w = World::new();
|
||||||
|
w.set_chunk(0, 0, Chunk::generate(|lx, ly| if (lx, ly) == (5, 4) { 146 } else { 1 }));
|
||||||
|
w
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step_window(sim: &mut Sim) {
|
||||||
|
for _ in 0..TICKS_PER_MOVE {
|
||||||
|
sim.step();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_step_per_window_and_replacement() {
|
||||||
|
let mut sim = Sim::new(open_world());
|
||||||
|
let id = sim.world.spawn_entity(0, (4, 4), 1);
|
||||||
|
sim.set_action(id, player_action::SOUTH);
|
||||||
|
sim.set_action(id, player_action::NORTH); // replaces
|
||||||
|
sim.step(); // no window yet
|
||||||
|
assert_eq!(sim.world.entities[&id].pos, (4, 4));
|
||||||
|
step_window(&mut sim);
|
||||||
|
assert_eq!(sim.world.entities[&id].pos, (4, 3));
|
||||||
|
step_window(&mut sim); // intent consumed — no second step
|
||||||
|
assert_eq!(sim.world.entities[&id].pos, (4, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn blocked_and_out_of_world_steps_are_rejected() {
|
||||||
|
let mut sim = Sim::new(open_world());
|
||||||
|
let id = sim.world.spawn_entity(0, (4, 4), 1);
|
||||||
|
sim.set_action(id, player_action::EAST); // into the wall at (5, 4)
|
||||||
|
step_window(&mut sim);
|
||||||
|
assert_eq!(sim.world.entities[&id].pos, (4, 4));
|
||||||
|
|
||||||
|
let edge = sim.world.spawn_entity(0, (0, 0), 1);
|
||||||
|
sim.set_action(edge, player_action::WEST); // no chunk there
|
||||||
|
step_window(&mut sim);
|
||||||
|
assert_eq!(sim.world.entities[&edge].pos, (0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn noop_clears_intent() {
|
||||||
|
let mut sim = Sim::new(open_world());
|
||||||
|
let id = sim.world.spawn_entity(0, (4, 4), 1);
|
||||||
|
sim.set_action(id, player_action::SOUTH);
|
||||||
|
sim.set_action(id, player_action::NOOP);
|
||||||
|
step_window(&mut sim);
|
||||||
|
assert_eq!(sim.world.entities[&id].pos, (4, 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use crate::chunk::{Chunk, TileDef, TileFlags, PaletteFullError};
|
use crate::chunk::{Chunk, TileFlags};
|
||||||
use crate::entity::Entity;
|
use crate::entity::Entity;
|
||||||
|
|
||||||
pub fn tile_to_chunk(tx: i16, ty: i16) -> (i16, i16) {
|
pub fn tile_to_chunk(tx: i16, ty: i16) -> (i16, i16) {
|
||||||
@@ -45,11 +45,11 @@ impl World {
|
|||||||
self.chunks.get(&(cx, cy)).map(|c| c.tile_flags(lx, ly))
|
self.chunks.get(&(cx, cy)).map(|c| c.tile_flags(lx, ly))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_tile(&mut self, wx: i16, wy: i16, def: TileDef) -> Result<(), PaletteFullError> {
|
pub fn set_tile(&mut self, wx: i16, wy: i16, id: u16) {
|
||||||
let (cx, cy) = tile_to_chunk(wx, wy);
|
let (cx, cy) = tile_to_chunk(wx, wy);
|
||||||
let (lx, ly) = tile_to_local(wx, wy);
|
let (lx, ly) = tile_to_local(wx, wy);
|
||||||
let chunk = self.chunks.entry((cx, cy)).or_insert_with(Chunk::new);
|
let chunk = self.chunks.entry((cx, cy)).or_insert_with(Chunk::new);
|
||||||
chunk.set_tile(lx, ly, def)
|
chunk.set_tile(lx, ly, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_entity(&mut self, type_id: u16, pos: (i16, i16), hp: u16) -> u32 {
|
pub fn spawn_entity(&mut self, type_id: u16, pos: (i16, i16), hp: u16) -> u32 {
|
||||||
Reference in New Issue
Block a user