From c8cc6d27534d2597578d5f3abca0f404ae23b9aa Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Wed, 6 Jun 2012 15:31:12 -0400 Subject: [PATCH] examples: add a simple leapfrog game Signed-off-by: Jean Privat --- examples/leapfrog/Makefile | 2 + examples/leapfrog/curses.nit | 43 ++++++ examples/leapfrog/curses.nit.c | 89 +++++++++++++ examples/leapfrog/curses.nit.h | 21 +++ examples/leapfrog/game.nit | 115 ++++++++++++++++ examples/leapfrog/leapfrog.nit | 288 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 558 insertions(+) create mode 100644 examples/leapfrog/Makefile create mode 100644 examples/leapfrog/curses.nit create mode 100644 examples/leapfrog/curses.nit.c create mode 100644 examples/leapfrog/curses.nit.h create mode 100644 examples/leapfrog/game.nit create mode 100644 examples/leapfrog/leapfrog.nit diff --git a/examples/leapfrog/Makefile b/examples/leapfrog/Makefile new file mode 100644 index 0000000..73efac5 --- /dev/null +++ b/examples/leapfrog/Makefile @@ -0,0 +1,2 @@ +leapfrog: + ../../bin/nitc leapfrog.nit --cc-lib-name curses diff --git a/examples/leapfrog/curses.nit b/examples/leapfrog/curses.nit new file mode 100644 index 0000000..af98bcb --- /dev/null +++ b/examples/leapfrog/curses.nit @@ -0,0 +1,43 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Curses for Nit +module curses + +# A curse windows +extern Window + # Initialize the screen + new is extern "initscr" + + # print a string somewhere + # NOTE: as with the curses API, the position is (y,x) + fun mvaddstr(y,x: Int, str: String) is extern import String::to_cstring + + fun refresh is extern + fun wclear is extern + fun delwin is extern + fun endwin is extern +end + +redef class Sys + # Wait a specific number of second and nanoseconds + # FIXME: should not be in the curses module + fun nanosleep(sec, nanosec: Int) is extern +end + +redef class Stdin + # Is these something to read? (non blocking) + # FIXME: should not be in the curses module + fun poll_in: Bool is extern +end diff --git a/examples/leapfrog/curses.nit.c b/examples/leapfrog/curses.nit.c new file mode 100644 index 0000000..0e30dbb --- /dev/null +++ b/examples/leapfrog/curses.nit.c @@ -0,0 +1,89 @@ +/* + Extern implementation of Nit module curses + File initially created by nits to customize type of extern classes +*/ + +#include "curses.nit.h" +#include +#include + +/* +C implementation of curses::Window::init +*/ +Window new_Window___impl( ) +{ + WINDOW *res; + res = initscr(); + if (res == NULL) { + fprintf(stderr, "Error initialising ncurses.\n"); + exit(EXIT_FAILURE); + } + raw(); + keypad(res, TRUE); + noecho(); +} + +/* +C implementation of curses::Window::mvaddstr + +Imported methods signatures: + char * String_to_cstring( String recv ) for string::String::to_cstring +*/ +void Window_mvaddstr___impl( Window recv, bigint y, bigint x, String str ) +{ + char *c_string = String_to_cstring( str ); + mvaddstr(y, x, c_string); +} + +/* +C implementation of curses::Window::refresh +*/ +void Window_refresh___impl( Window recv ) +{ + refresh(); +} + +/* +C implementation of curses::Window::wclear +*/ +void Window_wclear___impl( Window recv ) { + wclear(recv); +} + +/* +C implementation of curses::Window::delwin +*/ +void Window_delwin___impl( Window recv ) +{ + delwin(recv); +} + +/* +C implementation of curses::Window::endwin +*/ +void Window_endwin___impl( Window recv ) +{ + endwin(); +} + +/* +C implementation of curses::Sys::nanosleep +*/ +void Sys_nanosleep___impl( Sys recv, bigint sec, bigint nanosec ) { + const struct timespec req = {sec, nanosec}; + nanosleep(&req, NULL); +} + +/* +C implementation of curses::Stdin::poll_in +*/ +int Stdin_poll_in___impl( Stdin recv ) { + struct pollfd fd = {0, POLLIN, 0}; + int res = poll(&fd, 1, 0); + if (res == -1) { + endwin(); + perror("Error poll stdin"); + exit(EXIT_FAILURE); + } + return res > 0; +} diff --git a/examples/leapfrog/curses.nit.h b/examples/leapfrog/curses.nit.h new file mode 100644 index 0000000..6318fc4 --- /dev/null +++ b/examples/leapfrog/curses.nit.h @@ -0,0 +1,21 @@ +/* + Extern implementation of Nit module curses + File initially created by nits to implement extern methods body +*/ + +#ifndef CURSES_NIT_H +#define CURSES_NIT_H + +#include +#include + +#define Window void* +Window new_Window___impl( ); +void Window_mvaddstr___impl( Window recv, bigint y, bigint x, String str ); +void Window_refresh___impl( Window recv ); +void Window_wclear___impl( Window recv ); +void Window_delwin___impl( Window recv ); +void Window_endwin___impl( Window recv ); +void Sys_nanosleep___impl( Sys recv, bigint sec, bigint nanosec ); +int Stdin_poll_in___impl( Stdin recv ); +#endif diff --git a/examples/leapfrog/game.nit b/examples/leapfrog/game.nit new file mode 100644 index 0000000..a0e2a83 --- /dev/null +++ b/examples/leapfrog/game.nit @@ -0,0 +1,115 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# 2D management of game elements +# +# FIXME: game is a bad name +# TODO: collision framework (with quad tree?) +module game + +# The root class of the living objects (sprites, group of sprites, etc.) +abstract class LiveObject + # Compute the position, state and appearance. + fun update do end + + # Controls whether `update' and `draw' are automatically called by `LiveGroup' + var exists writable = true + + # Redefine this method to asks how to draw on a view + fun draw(view: View) is abstract +end + + +# The basic atomic living and moving object. +# +# A sprite has a position and a velocity +class Sprite + super LiveObject + + # x coordinate of the top-left point + var x: Int writable = 0 + + # y coordinate of the top-left point + var y: Int writable = 0 + + # width of the sprite + var width: Int writable = 100 + + # height of the sprite + var height: Int writable = 100 + + # x velocity (applied by `update') + var vx: Int writable = 0 + + # y velocity (applied by `update') + var vy: Int writable = 0 + + redef fun update + do + self.x += self.vx + self.y += self.vy + end + + redef fun draw(view) do view.draw_sprite(self) + + # Is self overlaps (or contains) an other sprite + # `x', `y', `width', and `height' of both sprites are considered + fun overlaps(other: Sprite): Bool + do + return self.x+self.width > other.x and self.x < other.x+other.width and self.y+self.height > other.y and self.y < other.y+other.width + end +end + +# Organizational class to manage groups of sprites and other live objects. +class LiveGroup[E: LiveObject] + super LiveObject + super List[E] + + init + do + end + + # Recursively update each live objects that `exists' + redef fun update + do + for x in self do if x.exists then x.update + end + + # Recursively draw each live objects that `exists' + redef fun draw(view) + do + for x in self do if x.exists then x.draw(view) + end +end + +# Abstract view do draw sprites +# +# Concrete views are specific for each back-end. +# View can also be used to implements camera and other fun things. +interface View + # Draw a specific sprite on the view + # + # This method must be implemented for each specific view. + # A traditional way of implementation is to use a double-dispatch mechanism + # + # Exemple: + # class MyView + # redef fun draw_sprite(s) do s.draw_on_myview(self) + # end + # redef class Sprite + # # How to draw a sprite on my specific view + # fun draw_on_myview(myview: MyView) is abstract + # end + fun draw_sprite(s: Sprite) is abstract +end diff --git a/examples/leapfrog/leapfrog.nit b/examples/leapfrog/leapfrog.nit new file mode 100644 index 0000000..cfecc7e --- /dev/null +++ b/examples/leapfrog/leapfrog.nit @@ -0,0 +1,288 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# game of leapfrog: be a sheep and avoid the duck to grab apples +# +# This module is an example of a simple game using a curses backend +module leapfrog + +import curses +import game + +# A simple game view in a curses windows +class CursesView + super View + + # The associated curses window + var window: Window + + redef fun draw_sprite(s: Sprite) do s.draw_on_curses(window) +end + +redef class Sprite + # Drawing of a sprite in the curse windows + fun draw_on_curses(window: Window) is abstract +end + +# A falling apple +# If the sheep grab it, it scores one point. +class Apple + super Sprite + + redef fun draw_on_curses(window) + do + var x = self.x/100 + var y = self.y/100 + window.mvaddstr(y, x, "o") + end +end + +# Common class for the sheep and the duck +class Animal + super Sprite + + # Is the sprite stunned? + # 0 if no, >0 if stunned. + # The value indicate the number of step that remain to be stunt + # + # If a animal is stunned, it cannot move horizontally + var stunt_ttl: Int writable = 0 + + # Common update for animal + # handle stunt and edge collision + redef fun update + do + if stunt_ttl > 0 then + stunt_ttl -= 1 + else + self.x += self.vx + end + self.y += self.vy + + if self.x + self.width > 7900 then + self.vx = -self.vx + self.x = 7900 - self.width + else if self.x < 0 then + self.vx = -self.vx + self.x = 0 + end + end +end + +# The player sprite +class Sheep + super Animal + + # Which frame to show when walking + var leg_state: Int = 0 + + # Is the sheep currently jumping (or falling) + var is_jumping = false + + init + do + self.y = 2000 - 200 + self.vx = 100 + self.width = 600 + self.height = 200 + end + + redef fun update + do + super + + if is_jumping then + if self.y + self.height > 2000 then + is_jumping = false + self.vy = 0 + self.y = 2000 - self.height + else + self.vy += 10 # gravity + end + end + if not is_jumping then + self.leg_state = 1 - self.leg_state # change leg + end + end + + # Try to jump is possible (ie if walking and not stunt) + fun jump + do + if is_jumping or stunt_ttl > 0 then return + self.vy = -100 + self.is_jumping = true + end + + redef fun draw_on_curses(window) + do + var x = self.x/100 + var y = self.y/100 + if self.is_jumping then + if self.vy > 0 then + # falling + if self.vx > 0 then + window.mvaddstr(y, x, "'---@>") + window.mvaddstr(y+1, x, " \\-\\'") + else + window.mvaddstr(y, x, "<@---'") + window.mvaddstr(y+1, x, " '/-/") + end + else + # jumping + if self.vx > 0 then + window.mvaddstr(y, x, ",---@>") + window.mvaddstr(y+1, x, " /-/'") + else + window.mvaddstr(y, x, "<@---,") + window.mvaddstr(y+1, x, " '\\-\\") + end + end + else if self.vx > 0 then + if self.leg_state == 0 then + window.mvaddstr(y, x, ",---@>") + window.mvaddstr(y+1, x, " /-|'") + else + window.mvaddstr(y, x, ",---@>") + window.mvaddstr(y+1, x, " |-\\'") + end + else + if self.leg_state == 0 then + window.mvaddstr(y, x, "<@---,") + window.mvaddstr(y+1, x, " '/-|") + else + window.mvaddstr(y, x, "<@---,") + window.mvaddstr(y+1, x, " '|-\\") + end + end + end +end + +# The nemesis of the sheep. +# It just go back and forth +class Duck + super Animal + init + do + self.y = 2000 - 200 + self.x = 7900 - 400 + self.vx = -80 + self.width = 400 + self.height = 200 + end + + redef fun draw_on_curses(window) + do + var x = self.x/100 + var y = self.y/100 + + if self.vx > 0 then + window.mvaddstr(y, x, " @<") + window.mvaddstr(y+1, x, "<__)") + else + window.mvaddstr(y, x, ">@") + window.mvaddstr(y+1, x, "(__>") + end + end +end + +class Game + fun run(window: Window) + do + var main_view = new CursesView(window) + + var apples = new LiveGroup[Apple] + var duck = new Duck + var sheep = new Sheep + + var score = 0 + + var sprites = new LiveGroup[LiveObject] + sprites.add(apples) + sprites.add(duck) + sprites.add(sheep) + + loop + + # Call update on all sprites + sprites.update + + # Need a new apple + if 10.rand < 2 then + var a = new Apple + a.x = (60.rand + 10) * 100 + a.y = 0 + a.vx = 0 + a.vy = 70.rand + 30 + apples.add(a) + end + + # Eat apple or fallen apple + for a in apples do + if not a.exists then continue + if a.overlaps(sheep) then + score += 1 + a.exists = false + end + if a.y > 2000 then + a.exists = false + end + end + + # Sheep vs duck + if sheep.overlaps(duck) then + if sheep.is_jumping and sheep.vy > 0 then + duck.stunt_ttl = 5 + sheep.vy = -150 + else + if sheep.x < duck.x then + sheep.x = duck.x - sheep.width + else + sheep.x = duck.x + duck.width + end + sheep.vx = - sheep.vx + duck.vx = - duck.vx + end + end + + # Redraw the screen + window.wclear + sprites.draw(main_view) + window.mvaddstr(0, 0, "'q' to quit - score: {score}") + window.mvaddstr(20, 0, "#"*80) + window.refresh + + # Wait the next frame + sys.nanosleep(0, 48000000) + + # Keyboard input + while stdin.as(Stdin).poll_in do + if stdin.eof then return + var c = stdin.read_char + if c == 'q'.ascii then return + sheep.jump + end + end + end +end + +var game = new Game + +var win = new Window + +game.run(win) + +win.delwin +win.endwin +win.refresh + -- 1.7.9.5