-
Notifications
You must be signed in to change notification settings - Fork 127
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #146 from d4rk/snake
New Game – Snake
- Loading branch information
Showing
7 changed files
with
562 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
local log = require("vim-be-good.log") | ||
local GameUtils = require("vim-be-good.game-utils") | ||
local SnakeGame = require("vim-be-good.games.snakelib.snakegame") | ||
local T = require("vim-be-good.types") | ||
|
||
local Snake = {} | ||
|
||
function Snake:new(difficulty, window) | ||
local getDifficultyLevel = function(d) | ||
for i, val in ipairs(T.difficulty) do | ||
if d == val then | ||
return i | ||
end | ||
end | ||
return 0 | ||
end | ||
local round = { | ||
window = window, | ||
difficulty = difficulty, | ||
difficultyLevel = getDifficultyLevel(difficulty), | ||
endRoundCallback = nil | ||
} | ||
self.__index = self | ||
return setmetatable(round, self) | ||
end | ||
|
||
function Snake:getInstructions() | ||
return { | ||
'', | ||
'Classic game of Snake.', | ||
'', | ||
'1. h,j,k,l to navigate', | ||
' h - move left', | ||
' j - move down', | ||
' k - move up ', | ||
' l - move right', | ||
'2. Eat food (O) to grow', | ||
'3. Don\'t eat yourself', | ||
'4. In higher difficulties, walls kill', | ||
'5. Snake speed scales with difficulty', | ||
} | ||
end | ||
|
||
function Snake:getConfig() | ||
log.info("getConfig", self.difficulty, GameUtils.difficultyToTime[self.difficulty]) | ||
return { | ||
roundTime = 1000000, | ||
noCursor = true, | ||
canEndRound = true, | ||
} | ||
end | ||
|
||
function Snake:checkForWin() | ||
log.info('Checking for Win') | ||
return false | ||
end | ||
|
||
function Snake:name() | ||
return 'snake' | ||
end | ||
|
||
function Snake:setEndRoundCallback(endRoundCallback) | ||
self.endRoundCallback = endRoundCallback | ||
end | ||
|
||
function Snake:render() | ||
if self.snakeGame then | ||
self.snakeGame:shutdown(nil) | ||
end | ||
self.snakeGame = SnakeGame:new(35, 15, self.difficultyLevel, self.endRoundCallback) | ||
self.snakeGame:start() | ||
return {''}, 1 | ||
end | ||
|
||
return Snake |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
local STOPPED=0 | ||
local UP = 1 | ||
local DOWN=2 | ||
local LEFT=3 | ||
local RIGHT=4 | ||
|
||
local Directions = { | ||
STOPPED = 0, | ||
UP = 1, | ||
DOWN = 2, | ||
LEFT = 3, | ||
RIGHT = 4, | ||
} | ||
|
||
local HeadChar = { | ||
[UP] = '^', | ||
[DOWN] = 'v', | ||
[LEFT] = '<', | ||
[RIGHT] = '>', | ||
[STOPPED] = 'X', | ||
} | ||
|
||
local KeyDirMap = { | ||
h = LEFT, | ||
j = DOWN, | ||
k = UP, | ||
l = RIGHT | ||
} | ||
|
||
return { | ||
Directions = Directions, | ||
HeadChar = HeadChar, | ||
KeyDirMap = KeyDirMap, | ||
BodyChar = '*', | ||
FoodChar = 'O', | ||
GridChar = '.', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
local V = vim.api | ||
local C = require('vim-be-good.games.snakelib.const') | ||
local log = require("vim-be-good.log") | ||
|
||
local Grid = {} | ||
|
||
-- Class the represents a textual rendering grid. | ||
-- @param bufNum integer: the underlying vim buffer to use | ||
-- @param cols integer: the number of columns (width) | ||
-- @param rows integer: the number of rows (height) | ||
-- @param fillChar string: the default character to use | ||
-- when clearing the grid | ||
function Grid:new(bufNum, cols, rows, fillChar) | ||
self.__index = self | ||
if not fillChar then | ||
fillChar = C.GridChar | ||
end | ||
local lines = {} | ||
for _ = 1, rows do | ||
table.insert(lines, string.rep(fillChar, cols)) | ||
end | ||
local newGrid = { | ||
buf = bufNum, | ||
lines = lines, | ||
rows = rows, | ||
cols = cols, | ||
width = cols, | ||
height = rows, | ||
fillChar = fillChar | ||
} | ||
return setmetatable(newGrid, self) | ||
end | ||
|
||
-- Clears the grid using the given @param fillChar, or the | ||
-- default fillChar if none is provided | ||
function Grid:clear(fillChar) | ||
if not fillChar then | ||
fillChar = self.fillChar | ||
end | ||
local lines = {} | ||
for _ = 1, self.rows do | ||
table.insert(lines, string.rep(fillChar, self.cols)) | ||
end | ||
self.lines = lines | ||
end | ||
|
||
-- Sets the character at the given position to @param char | ||
function Grid:setChar(col, row, char) | ||
row = row + 1 | ||
col = col + 1 | ||
if row < 1 or col < 1 or row > self.rows or col > self.cols then | ||
log.warn("setChar ("..char..") out of bounds: " .. col .. "," .. row) | ||
return | ||
end | ||
local line = self.lines[row] | ||
line = string.sub(line, 1, col-1) .. char .. string.sub(line, col+1) | ||
self.lines[row] = line | ||
end | ||
|
||
-- Returns the character at the given position. | ||
function Grid:getChar(col, row) | ||
row = row + 1 | ||
col = col + 1 | ||
if row < 1 or col < 1 or row > self.rows or col > self.cols then | ||
log.warn("getChar out of bounds: " .. col .. "," .. row) | ||
return nil | ||
end | ||
local line = self.lines[row] | ||
return string.sub(line, col, col) | ||
end | ||
|
||
-- Draws the grid to the text buffer associated with the grid. | ||
-- The buffer is marked non-modifiable at the end of the render. | ||
function Grid:render() | ||
local currentLines = V.nvim_buf_get_lines(self.buf, 0, -1, false) | ||
local rows = #currentLines | ||
local maxCols = 0 | ||
for _, row in pairs(currentLines) do | ||
local lineLen = string.len(row) | ||
if lineLen > maxCols then | ||
maxCols = lineLen | ||
end | ||
end | ||
V.nvim_buf_set_option(self.buf, 'modifiable', true) | ||
V.nvim_buf_set_text(self.buf, 0, 0, rows-1, maxCols, self.lines) | ||
V.nvim_buf_set_option(self.buf, 'modifiable', false) | ||
end | ||
|
||
return Grid |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
local Snake = {} | ||
|
||
local C = require('vim-be-good.games.snakelib.const') | ||
local Dir = C.Directions | ||
|
||
local STOPPED = Dir.STOPPED | ||
local UP = Dir.UP | ||
local DOWN = Dir.DOWN | ||
local LEFT = Dir.LEFT | ||
local RIGHT = Dir.RIGHT | ||
|
||
-- Represents the snake object (consists of a head and a body). | ||
-- @param x integer: the x coordinate of the head | ||
-- @param y integer: the y coordinate of the head | ||
-- @param initialSize integer: the initial size of the body | ||
-- @param noWalls boolean: if true, then the snake loops | ||
-- around when it hits a wall | ||
function Snake:new(x, y, initialSize, noWalls) | ||
self.__index = self | ||
local head = { x = math.floor(x), y = math.floor(y) } | ||
local newSnake = { | ||
dir = RIGHT, | ||
head = {x = math.floor(x), y = math.floor(y)}, | ||
body = {}, | ||
shouldGrow = false, | ||
dead = false, | ||
noWalls = noWalls, | ||
hitWall = false, | ||
} | ||
if initialSize and initialSize > 1 then | ||
local lastBodyPart = head | ||
for _ = 1, initialSize do | ||
local bodyPart = { x = lastBodyPart.x - 1, y = lastBodyPart.y } | ||
lastBodyPart = bodyPart | ||
table.insert(newSnake.body, bodyPart) | ||
end | ||
end | ||
return setmetatable(newSnake, self) | ||
end | ||
|
||
-- Sets the direction of the snake. | ||
-- Snakes can't reverse their direction, so not all | ||
-- combinations are valid. | ||
-- @param dir Direction: one of the direction enums | ||
function Snake:setDir(dir) | ||
if self.dead or dir < UP or dir > RIGHT then | ||
return | ||
end | ||
local curDir = self.dir | ||
-- Snakes can't reverse | ||
if curDir == LEFT and dir == RIGHT or | ||
curDir == RIGHT and dir == LEFT or | ||
curDir == UP and dir == DOWN or | ||
curDir == DOWN and dir == UP then | ||
return | ||
end | ||
self.dir = dir | ||
end | ||
|
||
-- Notifies that snake that it should grow its body | ||
-- length by 1. Called when it eats food. | ||
function Snake:grow() | ||
self.shouldGrow = true | ||
end | ||
|
||
-- Moves the snake in its current direction, and updates its | ||
-- internal state. Takes into account walls, growth, etc. | ||
-- @param grid Grid: the grid object that the snake can move it | ||
function Snake:tick(grid) | ||
local head = self.head | ||
local dir = self.dir | ||
if dir == STOPPED then | ||
return | ||
end | ||
-- Move tail to current head | ||
if #self.body > 0 then | ||
local tail = {} | ||
if not self.shouldGrow then | ||
tail = table.remove(self.body) | ||
end | ||
self.shouldGrow = false | ||
tail.x = head.x | ||
tail.y = head.y | ||
table.insert(self.body, 1, tail) | ||
end | ||
-- Move head in direction | ||
if dir == UP then | ||
head.y = head.y - 1 | ||
elseif dir == DOWN then | ||
head.y = head.y + 1 | ||
elseif dir == LEFT then | ||
head.x = head.x - 1 | ||
elseif dir == RIGHT then | ||
head.x = head.x + 1 | ||
end | ||
-- Loop around if at edge of screen | ||
if head.x >= grid.width then | ||
self:handleWallHit(function () head.x = 0 end) | ||
elseif head.x < 0 then | ||
self:handleWallHit(function () head.x = grid.width end) | ||
elseif head.y >= grid.height then | ||
self:handleWallHit(function () head.y = 0 end) | ||
elseif head.y < 0 then | ||
self:handleWallHit(function () head.y = grid.height end) | ||
else | ||
self.hitWall = false | ||
end | ||
end | ||
|
||
-- Handles collision with walls | ||
function Snake:handleWallHit(noWallsCallback) | ||
if self.noWalls then | ||
-- If no walls, execute the callback, which loops the head | ||
-- to the opposite edge | ||
noWallsCallback() | ||
end | ||
self.hitWall = true | ||
end | ||
|
||
-- Draws the snake on the provided grid. | ||
function Snake:renderBody(grid) | ||
for _, body in pairs(self.body) do | ||
grid:setChar(body.x, body.y, C.BodyChar) | ||
end | ||
end | ||
|
||
-- Draws the snake's head on the provide grid. | ||
function Snake:renderHead(grid) | ||
local head = self.head | ||
grid:setChar(head.x, head.y, C.HeadChar[self.dir]) | ||
end | ||
|
||
-- Called when the snake has met an untimely demise. | ||
function Snake:oops() | ||
self.dead = true | ||
self.dir = STOPPED | ||
end | ||
|
||
return Snake |
Oops, something went wrong.