Index: nogue.nimble ================================================================== --- nogue.nimble +++ nogue.nimble @@ -6,8 +6,8 @@ license = "MIT" # Dependencies requires "nim >= 0.16.0" -requires "ncurses" +requires "https://github.com/zacharycarter/blt-nim.git" bin = @["nogue"] Index: noguepkg/base.nim ================================================================== --- noguepkg/base.nim +++ noguepkg/base.nim @@ -1,13 +1,12 @@ -import ncurses, random, options, deques, strutils +import random, options, deques, strutils import map, characters type Game* = ref GameObj GameObj = object y*, x*, ticks*: int - gameWindow*: ptr window map*: Map charMap*: Matrix[Character] characters*: seq[Character] messages*: Deque[Message] mode*: GameMode Index: noguepkg/characters.nim ================================================================== --- noguepkg/characters.nim +++ noguepkg/characters.nim @@ -17,11 +17,11 @@ of ckBeast: beastKind*: bcKind ch*: char of ckPlayer: playerKind*: pcKind - color*: int64 + colorName*: string attrs*: seq[int64] maxHealth*: int bcKind* = enum wolf, bear, stag pcKind* = enum @@ -31,11 +31,11 @@ hlHealthy = "healthy" hlWounded = "wounded" hlCritical = "critically injured" proc kind*(c: Character): CharacterKind = c.class.kind -proc color*(c: Character): int64 = c.class.color +proc colorName*(c: Character): string = c.class.colorName proc ch*(c: Character): char = c.class.ch proc isPC*[T](c: T): bool {. noSideEffect, inline .} = c.kind == ckPlayer proc verb*(c: Character, v: string): string = Index: noguepkg/classes.nim ================================================================== --- noguepkg/classes.nim +++ noguepkg/classes.nim @@ -1,41 +1,41 @@ -import ncurses, options, random +import blt, options, random import map, characters let WOLF = Class( kind: ckBeast, beastKind: wolf, ch: 'W', - color: COLOR_PAIR 0 + colorName: "gray" ) STAG = Class( kind: ckBeast, beastKind: stag, ch: 'S', - color: COLOR_PAIR 1 + colorName: "brown" ) BEAR = Class( kind: ckBeast, beastKind: bear, ch: 'B', - color: COLOR_PAIR 5 + colorName: "brown" ) PRIEST = Class( kind: ckPlayer, playerKind: priest, - color: COLOR_PAIR 0 + colorName: "white" ) WARRIOR = Class( kind: ckPlayer, playerKind: warrior, - color: COLOR_PAIR 2 + colorName: "brown" ) THIEF = Class( kind: ckPlayer, playerKind: thief, - color: COLOR_PAIR 3 + colorName: "dark gray" ) BEAST_CLASSES = [ WOLF, STAG, BEAR ] PLAYER_CLASSES = [ @@ -54,28 +54,25 @@ if character.isPC: '@' else: character.ch -proc attrs*(cell: Cell): seq[int64] = +proc fullColorName*(cell: Cell): string = case cell.kind: - of ckWall: @[COLOR_PAIR 0] + of ckWall: "gray" of ckFloor: case cell.floorKind: - of fkGrass: @[COLOR_PAIR 4] - of fkStone: @[COLOR_PAIR 0] + of fkGrass: "green" + of fkStone: "gray" -proc attrs*(character: Character): seq[int64] = +proc fullColorName*(character: Character): string = let - level = case character.level: - of elevated: some(A_BOLD) - of divine: some(A_UNDERLINE) - of normal: none(int) - if level.isSome: - @[character.color, level.get] - else: - @[character.color] + levelModifier = case character.level: + of elevated: "lighter" + of divine: "light" + of normal: "" + levelModifier & " " & character.colorName proc randomPC*(): Character = Character( class: random(PLAYER_CLASSES), health: random(20..60), Index: noguepkg/map.nim ================================================================== --- noguepkg/map.nim +++ noguepkg/map.nim @@ -4,11 +4,11 @@ CellKind* = enum ckWall, ckFloor FloorKind* = enum fkGrass, fkStone Cell* = object - point: Point + point*: Point case kind*: CellKind of ckWall: discard of ckFloor: floorKind*: FloorKind Matrix*[T] = seq[seq[T]] @@ -16,11 +16,11 @@ MapMatrix = Matrix[Cell] Dim* = enum y, x Point* = array[y..x, int] -proc `[]`*[T](matrix: Matrix[T], y, x: int): T = +proc `[]`* [T](matrix: Matrix[T], y, x: int): T = matrix[y][x] proc `[]`*[T](matrix: Matrix[T], p: Point): T = matrix[p[y]][p[x]] proc `[]=`*[T](matrix: var Matrix[T], y, x: int, t: T) = matrix[y][x] = t Index: noguepkg/ui.nim ================================================================== --- noguepkg/ui.nim +++ noguepkg/ui.nim @@ -1,6 +1,7 @@ -import ncurses, tables, deques, strutils, sequtils +import tables, deques, strutils, sequtils +import blt import map, base, characters, classes type UiCommand = enum ucExamine @@ -7,142 +8,116 @@ 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 -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() - - game.y = cropY - game.x = maxX - game.gameWindow = gameWindow - - 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) - const - colorInfo = COLOR_PAIR 0 - colorAlert = COLOR_PAIR 1 - colorSuccess = COLOR_PAIR 4 - 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] = @[]) = + MAXY = 25 + MAXX = 80 + WINDOWY = 35 + +proc init*(game: Game) = + discard terminal_open() + discard terminal_set("window.title='nogue'; window.size=80x35") + game.x = MAXX + game.y = MAXY + +let + colorInfo = color_from_name("white") + colorAlert = color_from_name("red") + colorSuccess = color_from_name("green") + +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: - attron attr - t.display(ch) - for attr in attrs: - attroff attr - -proc render(msg: Message) = + colorName = t.fullColorName + terminal_color colorName.color_from_name + terminal_put(t.point[x].cint, t.point[y].cint, ch.cint) + +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) - attroff color + terminal_color color + discard terminal_print(x, y, msg.content.cstring) 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 - move(i+1, 0) + terminal_layer(1) + for y in 0..game.map[].high: + let row = game.map[y] + for x in 0..row.high: + render row[x] # 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() - insertln() + terminal_layer(0) 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]) - move(game.messageRow, 0) - addstr targetCharacter.charInfo + render(targetCharacter) + 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 @@ -163,16 +138,15 @@ game.findExamineTarget(currentPoint, mNextPoint) of ecNormal: game.mode = gmNormal proc cycle*(game: var Game): Command = - var - success: int # Refresh display and get input. display game - success = refresh() - let input = getch().char + terminal_refresh() + var input = terminal_read() + echo "INPUT: ", input # Handle player input. result = Command.noOp case game.mode: of gmNormal: if input in uiCommands: @@ -184,6 +158,6 @@ if input in examineCommands: let examineCommand = examineCommands[input] game.handle(examineCommand) proc cleanUp*() = - endwin() + terminal_close()