1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
-
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
-
-
-
+
+
-
-
-
-
-
-
-
+
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
+
-
-
-
-
+
-
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
-
+
+
-
-
+
-
-
+
-
+
+
-
-
-
-
-
+
-
+
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
|
import ncurses, tables, deques, strutils, sequtils
import tables, deques, strutils, sequtils
import blt
import map, base, characters, classes
type
UiCommand = enum
ucExamine
ExamineCommand = enum
ecPrev, ecNext, ecNormal
const
gameCommands = {
'l': Command.up,
'k': Command.down,
'h': Command.left,
'e': Command.right,
'j': Command.upleft,
'u': Command.upright,
'b': Command.downleft,
'm': Command.downright,
'Q': quitGame
TK_U: Command.up,
TK_K: Command.down,
TK_H: Command.left,
TK_E: Command.right,
TK_L: Command.upleft,
TK_Y: Command.upright,
TK_B: Command.downleft,
TK_M: Command.downright,
TK_Q: quitGame
}.toTable
uiCommands = {
'x': ucExamine
TK_X: ucExamine
}.toTable
examineCommands = {
'<': ecPrev,
'>': ecNext,
27.char: ecNormal,
'x': ecNormal
TK_COMMA: ecPrev,
TK_PERIOD: ecNext,
TK_ESCAPE: ecNormal,
TK_X: ecNormal
}.toTable
var
examineTarget: Point
proc statusRow(game: Game): int = game.y
proc messageRow(game: Game): int = game.y + 1
const
MAXY = 25
MAXX = 80
WINDOWY = 35
proc init*(game: Game) =
var maxX, maxY, cropY: int
let gameWindow = initscr()
getMaxYX(gameWindow, maxY, maxX)
cropY = (maxX.float / 1.77).int
discard startColor()
discard cbreak()
discard terminal_open()
discard terminal_set("window.title='nogue'; window.size=80x35")
game.y = cropY
game.x = maxX
game.gameWindow = gameWindow
game.x = MAXX
game.y = MAXY
init_pair(0, COLOR_WHITE, COLOR_BLACK)
init_pair(1, COLOR_RED, COLOR_BLACK)
init_pair(2, COLOR_YELLOW, COLOR_BLACK)
init_pair(3, COLOR_MAGENTA, COLOR_BLACK)
init_pair(4, COLOR_GREEN, COLOR_BLACK)
init_pair(5, COLOR_BLUE, COLOR_BLACK)
let
const
colorInfo = COLOR_PAIR 0
colorAlert = COLOR_PAIR 1
colorSuccess = COLOR_PAIR 4
colorInfo = color_from_name("white")
colorAlert = color_from_name("red")
colorSuccess = color_from_name("green")
colorPlayer = COLOR_PAIR 2
colorBeast = COLOR_PAIR 3
converter chToInt(ch: char): chtype = ord(ch)
proc display(cell: Cell, ch: char) =
addch(ch)
proc display(character: Character, ch: char) =
mvaddch(character.point[y], character.point[x], ch)
proc render[T](t: T, extraAttrs: seq[int] = @[]) =
proc render[T: Cell | Character](t: T, extraAttrs: seq[int] = @[]) =
let
ch = t.displayChar
var attrs = t.attrs.mapIt(it.int) & extraAttrs
for attr in attrs:
colorName = t.fullColorName
terminal_color colorName.color_from_name
attron attr
t.display(ch)
for attr in attrs:
attroff attr
terminal_put(t.point[x].cint, t.point[y].cint, ch.cint)
proc render(msg: Message) =
proc render(x, y: cint, msg: Message) =
let color = case msg.level:
of mlInfo:
colorInfo
of mlAlert:
colorAlert
of mlSuccess:
colorSuccess
attron color
addstr(msg.content)
terminal_color color
discard terminal_print(x, y, msg.content.cstring)
attroff color
proc display(game: Game) =
## Render the whole game screen.
var msgBufferLength {.global.} = 0
# Render the map.
move(0, 0)
for i in 0..game.map[].high:
let row = game.map[i]
for cell in row:
render cell
terminal_layer(1)
for y in 0..game.map[].high:
let row = game.map[y]
for x in 0..row.high:
render row[x]
move(i+1, 0)
# Populate the map with all characters.
terminal_layer(2)
terminal_clear_area(0, 0, 80, 25)
for character in game.characters:
render character
# Render status line.
move(game.statusRow, 0)
deleteln()
terminal_layer(0)
insertln()
if game.mode != gmNormal:
let modeStr = $game.mode
addstr modeStr
discard terminal_print(0.cint, game.statusRow.cint, modeStr.cstring)
else:
addstr "----------"
discard terminal_print(0.cint, game.statusRow.cint, "----------".cstring)
# Message rendering
var onScreenMessages {.global.} = initDeque[Message]()
# Clear existing message rows.
move(game.messageRow, 0)
while msgBufferLength > 0:
deleteln()
msgBufferLength -= 1
# Render new message rows.
terminal_clear_area(0.cint, game.messageRow.cint, MAXX.cint, (WINDOWY-MAXY).cint)
case game.mode:
of gmExamine:
let targetCharacter = game.charMap[examineTarget]
if not targetCharacter.isNil:
render(targetCharacter, extraAttrs = @[A_BOLD])
render(targetCharacter)
move(game.messageRow, 0)
addstr targetCharacter.charInfo
discard terminal_print(0.cint, game.messageRow.cint, targetCharacter.charInfo)
msgBufferLength += 1
of gmNormal:
while game.messages.len > 0:
let
msgRow = game.messageRow + msgBufferLength
msg = game.messages.popLast()
move(msgRow, 0)
render msg
msgBufferLength += 1
if onScreenMessages.len > WINDOWY-MAXY:
onScreenMessages.popLast()
onScreenMessages.addFirst(game.messages.popLast())
var i = 0
for msg in onScreenMessages:
render(0.cint, (game.messageRow + i).cint, msg)
i += 1
proc handle(game: var Game, command: UiCommand) =
case command:
of ucExamine:
game.mode = gmExamine
proc findExamineTarget(game: Game, point: var Point, f: proc(p: var Point, y, x: int)) =
|