SVI 3 месяцев назад
Родитель
Сommit
c1f87cd4fe
14 измененных файлов с 544 добавлено и 291 удалено
  1. 2 2
      nimcity.nimble
  2. 0 43
      src/ball/ball.nim
  3. 100 246
      src/nimcity.nim
  4. 128 0
      src/pong/ball.nim
  5. 43 0
      src/pong/collision.nim
  6. 16 0
      src/pong/cons.nim
  7. 28 0
      src/pong/font.nim
  8. 14 0
      src/pong/input.nim
  9. 88 0
      src/pong/player.nim
  10. 70 0
      src/pong/pong.nim
  11. 0 0
      src/pong/pong_sdl.nim
  12. 2 0
      src/pong/render.nim
  13. 47 0
      src/pong/scores.nim
  14. 6 0
      src/pong/tmpl.nim

+ 2 - 2
nimcity.nimble

@@ -15,8 +15,8 @@ requires "sdl2"
 
 task prod, "Сборка для прода":
   # Exec запускает внешнюю команду (в данном случае компилятор Nim)
-  exec "nim c -d:release --opt:speed ./src/nimcity.nim"
+  exec "nim c --experimental:strictNotNil -d:release --opt:speed ./src/nimcity.nim"
 
 task dev, "Сборка для отладки":
   # Exec запускает внешнюю команду (в данном случае компилятор Nim)
-  exec "nim c -r -d:debug ./src/nimcity.nim"
+  exec "nim c -r --experimental:strictNotNil -d:debug ./src/nimcity.nim"

+ 0 - 43
src/ball/ball.nim

@@ -1,43 +0,0 @@
-import random
-
-import ../cons
-
-type
-  Ball* = ref object
-    x, y: float32
-    vx, vy: float32
-
-proc NewBall*(): Ball =
-  Ball(
-    x: WindowWidth / 2 - BallRadius,
-    y: WindowHeight / 2 - BallRadius,
-    vx: (rand(2.0) - 1) * 100,
-    vy: (rand(2.0) - 1) * 300
-  )
-
-proc `x`*(sf: Ball):float32{.inline.}=
-    return sf.x
-
-proc `dtx`*(sf: Ball, dt:float32){.inline.}=
-    sf.x += sf.vx * dt
-
-proc `y`*(sf: Ball):float32{.inline.}=
-    return sf.y
-
-proc `dty`*(sf: Ball, dt:float32){.inline.}=
-    sf.y += sf.vy * dt
-
-proc reset_y*(sf: Ball){.inline.}=
-    sf.y=0
-
-proc `vy`*(sf: Ball):var float32{.inline.}=
-    return sf.vy
-
-proc `y=`*(sf: Ball, val: float32){.inline.}=
-    sf.y=val
-
-proc `vx`*(sf: Ball):var float32{.inline.}=
-    return sf.vx
-
-proc `x=`*(sf: Ball, val: float32){.inline.}=
-    sf.x=val

+ 100 - 246
src/nimcity.nim

@@ -1,255 +1,109 @@
-import random
-
 import sdl2
 import sdl2/ttf
+# import sdl2/private/keycodes
 
-import ./cons
-import ./ball/ball
+from ./pong/cons import nil
+import ./pong/pong
+import ./pong/tmpl
+import ./pong/font
 
 type
-  Paddle = ref object
-    x, y: float32
-
-  Input {.pure.} = enum
-    Up,
-    Down,
-    None
-
-  Game = ref object
-    running: bool
-
-    inputs: array[Input, bool]
-    player, opponent: Paddle
-    ball: Ball
-
-    scores: tuple[player: uint, opponent: uint]
-
-proc newPaddle(x: float32): Paddle =
-  Paddle(
-    x: x,
-    y: (WindowHeight + PaddleHeight) / 2,
-  )
-
-
-
-func collision(ball: Ball, paddle: Paddle): bool =
-  return not (
-    ball.x > paddle.x + PaddleWidth or
-    ball.x + 2 * BallRadius < paddle.x or
-    ball.y > paddle.y + PaddleHeight or
-    ball.y + 2 * BallRadius < paddle.y
-  )
-
-proc updatePlayer(g: Game, dt: float32) =
-  if g.inputs[Input.Up]:
-    g.player.y -= PaddleSpeed * dt
-  if g.inputs[Input.Down]:
-    g.player.y += PaddleSpeed * dt
-
-  if g.player.y < 0:
-    g.player.y = 0
-  elif g.player.y + PaddleHeight > WindowHeight:
-    g.player.y = WindowHeight - PaddleHeight
-
-proc updateOpponent(g: Game, dt: float32) =
-  let dist = (g.ball.y + BallRadius) - (g.opponent.y + PaddleHeight / 2)
-  var dy: float32
-
-  if dist > 0:
-    dy = min(dist, PaddleSpeed * dt)
-  elif dist < 0:
-    dy = max(dist, -PaddleSpeed * dt)
-
-  g.opponent.y += dy
-  g.opponent.y = max(g.opponent.y, 0)
-  g.opponent.y = min(g.opponent.y, WindowHeight - PaddleWidth)
-
-proc bounce(v: var float32) =
-  v *= -1
-
-  v = max(v, -MaxBallComponentSpeed)
-  v = min(v,  MaxBallComponentSpeed)
-
-proc speedup(v: var float32) =
-  v *= (1 + rand(0.5))
-
-  v = max(v, -MaxBallComponentSpeed)
-  v = min(v,  MaxBallComponentSpeed)
-
-proc updateBall(g: Game, dt: float32) =
-  g.ball.dtx(dt)
-  g.ball.dty(dt)
-
-  # bounce on upper and lower borders, add speedup on bounce
-  if g.ball.y < 0:
-    g.ball.reset_y()
-    bounce(g.ball.vy)
-    speedup(g.ball.vx)
-  elif g.ball.y + 2 * BallRadius > WindowHeight:
-    g.ball.y = WindowHeight - 2 * BallRadius
-    bounce(g.ball.vy)
-    speedup(g.ball.vx)
-
-  # bounce on paddles
-  if g.ball.collision g.player:
-    g.ball.x = g.player.x + PaddleWidth
-    bounce(g.ball.vx)
-    speedup(g.ball.vy)
-  elif g.ball.collision g.opponent:
-    g.ball.x = g.opponent.x - 2 * BallRadius
-    bounce(g.ball.vx)
-    speedup(g.ball.vy)
-
-  # opponent scored
-  if g.ball.x + 2 * BallRadius < 0:
-    inc g.scores.opponent
-    g.ball = NewBall()
-
-  # player scored
-  elif g.ball.x > WindowWidth:
-    inc g.scores.player
-    g.ball = NewBall()
-
-
-proc draw(renderer: RendererPtr, paddle: Paddle) =
-  renderer.setDrawColor 255, 255, 255, 255 # white
-  var r = rect(
-    cint(paddle.x), cint(paddle.y),
-    cint(PaddleWidth), cint(PaddleHeight)
-  )
-  renderer.fillRect(r)
-
-proc draw(renderer: RendererPtr, ball: Ball) =
-  renderer.setDrawColor 255, 255, 255, 255 # white
-  var r = rect(
-    cint(ball.x), cint(ball.y),
-    cint(2 * BallRadius), cint(2 * BallRadius)
-  )
-  renderer.fillRect(r)
-
-proc drawScores(
-  renderer: RendererPtr, font: FontPtr, scores: tuple[player: uint, opponent: uint]
-) =
-  let
-    color = color(255, 255, 255, 0)
-    text = $scores.player & " : " & $scores.opponent
-    surface = ttf.renderTextSolid(font, cstring text, color)
-    texture = renderer.createTextureFromSurface(surface)
-
-  surface.freeSurface
-  defer: texture.destroy
-
-  var r = rect(
-    (WindowWidth - TextWidth) div 2,
-    0,
-    TextWidth,
-    TextHeight
-  )
-  renderer.copy texture, nil, addr r
-
-proc newGame(): Game =
-  Game(
-    running: true,
-    player: newPaddle(PaddleWidth),
-    opponent: newPaddle(WindowWidth - 2 * PaddleWidth),
-    ball: NewBall(),
-    scores: (0'u, 0'u)
-  )
-
-proc update(g: Game, dt: float32) =
-  g.updatePlayer dt
-  g.updateOpponent dt
-  g.updateBall dt
-
-proc draw(g: Game, renderer: RendererPtr, font: FontPtr) =
-  renderer.setDrawColor 0, 0, 0, 255 # black
-  renderer.clear()
-
-  renderer.draw(g.player)
-  renderer.draw(g.opponent)
-  renderer.draw(g.ball)
-
-  renderer.drawScores(font, g.scores)
-
-  renderer.present()
-
-func toInput(key: Scancode): Input =
-  case key
-  of SDL_SCANCODE_UP: Input.Up
-  of SDL_SCANCODE_DOWN: Input.Down
-  else: Input.None
-
-type SDLException = object of Defect
-
-template sdlFailIf(condition: typed, reason: string) =
-  if condition: raise SDLException.newException(
-    reason & ", SDL error " & $getError()
-  )
-
-proc main =
-  sdlFailIf(not sdl2.init(INIT_VIDEO or INIT_TIMER or INIT_EVENTS)):
-    "SDL2 initialization failed"
-  defer: sdl2.quit()
-
-  let window = createWindow(
-    title = "Pong",
-    x = SDL_WINDOWPOS_CENTERED,
-    y = SDL_WINDOWPOS_CENTERED,
-    w = WindowWidth,
-    h = WindowHeight,
-    flags = SDL_WINDOW_SHOWN
-  )
-
-  sdlFailIf window.isNil: "window could not be created"
-  defer: window.destroy()
-
-  let renderer = createRenderer(
-    window = window,
-    index = -1,
-    flags = Renderer_Accelerated or Renderer_PresentVsync or Renderer_TargetTexture
-  )
-  sdlFailIf renderer.isNil: "renderer could not be created"
-  defer: renderer.destroy()
-
-  sdlFailIf(not ttfInit()): "SDL_TTF initialization failed"
-  defer: ttfQuit()
-
-  let font = ttf.openFont("./assets/UbuntuMono-R.ttf", TextHeight)
-  sdlFailIf font.isNil: "font could not be created"
-
-  var
-    running = true
-    game = newGame()
-
-    dt: float32
-
-    counter: uint64
-    previousCounter: uint64
-
-  counter = getPerformanceCounter()
-
-  while running:
-    previousCounter = counter
-    counter = getPerformanceCounter()
-
-    dt = (counter - previousCounter).float / getPerformanceFrequency().float
-
-    var event = defaultEvent
-
-    while pollEvent(event):
-      case event.kind
-      of QuitEvent:
-        running = false
-        break
-
-      of KeyDown:
-        game.inputs[event.key.keysym.scancode.toInput] = true
-      of KeyUp:
-        game.inputs[event.key.keysym.scancode.toInput] = false
-      else:
+    App {.requiresInit.}= object
+        window:WindowPtr
+        renderer:RendererPtr
+        font:Font
+        isRun = true
+        game:Pong
+        dt: float32=0
+        counter: uint64
+        previousCounter: uint64=0
+        event:Event
+
+proc NewApp():App=
+    if_SDL_fail(not sdl2.init(INIT_VIDEO or INIT_TIMER or INIT_EVENTS)):
+        "SDL2 initialization failed"
+
+    let window = createWindow(
+        title = "PingPong",
+        x = SDL_WINDOWPOS_CENTERED,
+        y = SDL_WINDOWPOS_CENTERED,
+        w = cons.WindowWidth,
+        h = cons.WindowHeight,
+        flags = SDL_WINDOW_SHOWN
+    )
+
+    if_SDL_fail(window.isNil, "NewApp(): window could not be created")
+
+    let renderer = createRenderer(
+        window = window,
+        index = -1,
+        flags = Renderer_Accelerated or Renderer_PresentVsync or Renderer_TargetTexture
+    )
+    if_SDL_fail(renderer.isNil, "NewApp(): renderer could not be created")
+
+    if_SDL_fail(not ttfInit()): "NewApp(): SDL_TTF initialization failed"
+
+    let font0 = ttf.openFont("./assets/UbuntuMono-R.ttf", cons.TextHeight)
+    if_SDL_fail(font0.isNil, "NewApp(): font could not be created")
+    let font=NewFont(font0, renderer)
+
+    let game = NewPong(font, renderer)
+    let counter = getPerformanceCounter()
+    let sf=App(
+        window:window,
+        renderer:renderer,
+        font:font,
+        game:game,
+        counter:counter,
+        event:defaultEvent,
+    )
+    return sf
+
+proc loop(sf:var App){.inline.}=
+    case sf.event.kind
+    of QuitEvent:
+        sf.isRun = false
+    of KeyDown:
+        # game.sdlInput[event.key.keysym.scancode.toInput] = true
+        # game.sdlInput[event.key.keysym.scancode.toInput] = false
+        # Теперь безопасно проверяем конкретные клавиши
+        case sf.event.key.keysym.scancode
+        of SDL_SCANCODE_UP:
+            sf.game.keyUp(sf.dt)
+        of SDL_SCANCODE_DOWN:
+            sf.game.keyDown(sf.dt)
+        else:
+            discard
+    of KeyUp:
+        # Останавливаем движение, когда стрелки отпущены
+        case sf.event.key.keysym.scancode
+        of SDL_SCANCODE_UP, SDL_SCANCODE_DOWN:
+            sf.game.stopHuman()
+        else:
+            discard
+    else:
         discard
+    sf.game.update(sf.dt)
+
+proc run(sf:var App)=
+    defer: sdl2.quit()
+    defer: sf.window.destroy()
+    defer: sf.renderer.destroy()
+    defer: ttfQuit()
 
-    game.update(dt)
-    game.draw(renderer, font)
+    while sf.isRun:
+        sf.previousCounter = sf.counter
+        sf.counter = getPerformanceCounter()
+
+        sf.dt = (sf.counter - sf.previousCounter).float / getPerformanceFrequency().float
+
+        sf.event = defaultEvent
+        while pollEvent(sf.event):
+            sf.loop()
+        sf.game.draw()
+
+proc main =
+    var app=NewApp()
+    app.run()
 
 main()

+ 128 - 0
src/pong/ball.nim

@@ -0,0 +1,128 @@
+import random
+
+import sdl2
+
+import ./cons
+import ./scores
+
+type
+  Ball* {.requiresInit.}= object
+    x, y: float32
+    vx, vy: float32
+    renderer:RendererPtr
+    scores:Scores
+
+proc NewBall*(renderer:RendererPtr, scores:Scores): Ball =
+  Ball(
+    x: WindowWidth / 2 - BallRadius,
+    y: WindowHeight / 2 - BallRadius,
+    vx: (rand(2.0) - 1) * 100,
+    vy: (rand(2.0) - 1) * 300,
+    renderer:renderer,
+    scores:scores,
+  )
+
+proc reset(sf: var Ball)=
+    sf.x= WindowWidth / 2 - BallRadius
+    sf.y= WindowHeight / 2 - BallRadius
+    sf.vx= (rand(2.0) - 1) * 100
+    sf.vy= (rand(2.0) - 1) * 300
+
+proc upVx(sf:var Ball) =
+    sf.vx *= (1 + rand(0.5))
+
+    sf.vx = max(sf.vx, -cons.MaxBallComponentSpeed)
+    sf.vx = min(sf.vx,  cons.MaxBallComponentSpeed)
+
+proc downVx*(sf: var Ball) =
+    sf.vx *= -1
+
+    sf.vx = max(sf.vx, -MaxBallComponentSpeed)
+    sf.vx = min(sf.vx,  MaxBallComponentSpeed)
+
+proc upVy*(sf:var Ball) =
+  sf.vy *= (1 + rand(0.5))
+
+  sf.vy = max(sf.vy, -cons.MaxBallComponentSpeed)
+  sf.vy = min(sf.vy,  cons.MaxBallComponentSpeed)
+
+proc downVy*(sf: var Ball) =
+    sf.vy *= -1
+
+    sf.vy = max(sf.vy, -MaxBallComponentSpeed)
+    sf.vy = min(sf.vy,  MaxBallComponentSpeed)
+
+type
+    BallUpdateReq* {.requiresInit.}= object
+        dTime*:float32 ## Время между кадрами
+        distRobot*:float32 ## Дистанция между мячом и роботом
+        isColHuman*:bool ## Коллизия с человеком
+        isColRobot*:bool ## Коллизия с роботом
+        humanX*:float32 ## Позиция человек
+        robotX*:float32 ## Позиция робота
+
+proc update*(sf:var Ball, req: BallUpdateReq)=
+    var dy: float32
+
+    if req.distRobot > 0:
+        dy = min(req.distRobot, PaddleSpeed * req.dTime)
+    elif req.distRobot < 0:
+        dy = max(req.distRobot, -PaddleSpeed * req.dTime)
+
+    sf.x += sf.vx * req.dTime
+    sf.y += sf.vy * req.dTime
+
+    # bounce on upper and lower borders, add speedup on bounce
+    if sf.y < 0:
+        sf.y=0
+        sf.upVx()
+        sf.downVy()
+    elif sf.y + 2 * BallRadius > WindowHeight:
+        sf.y = WindowHeight - 2 * BallRadius
+        sf.upVx()
+        sf.downVy()
+
+    # check on collisions
+    if req.isColHuman:
+        sf.x = req.humanX + PaddleWidth
+        sf.downVx()
+        sf.upVy()
+    elif req.isColRobot:
+        sf.x = req.robotX - 2 * BallRadius
+        sf.downVx()
+        sf.upVy()
+
+    # robot scored
+    if sf.x + 2 * BallRadius < 0:
+        sf.scores.robot_inc()
+        sf.reset()
+    # human scored
+    elif sf.x > WindowWidth:
+        sf.scores.human_inc
+        sf.reset()
+
+proc draw*(sf: Ball) =
+  sf.renderer.setDrawColor 255, 255, 255, 255 # white
+  var r = rect(
+    cint(sf.x), cint(sf.y),
+    cint(2 * cons.BallRadius), cint(2 * cons.BallRadius)
+  )
+  sf.renderer.fillRect(r)
+
+proc `x`*(sf: Ball):float32{.inline.}=
+    return sf.x
+
+proc `y`*(sf: Ball):float32{.inline.}=
+    return sf.y
+
+proc `vy`*(sf: var Ball):var float32{.inline.}=
+    return sf.vy
+
+proc `y=`*(sf: var Ball, val: float32){.inline.}=
+    sf.y=val
+
+# proc `vx`*(sf: Ball):var float32{.inline.}=
+#     return sf.vx
+
+proc `x=`*(sf:var Ball, val: float32){.inline.}=
+    sf.x=val

+ 43 - 0
src/pong/collision.nim

@@ -0,0 +1,43 @@
+import ./player
+import ./ball
+import ./cons
+
+type
+    Collision*{.requiresInit.} = object
+        human:Player
+        robot:Player
+        ball:Ball
+        isColHuman:bool=false
+        isColRobot:bool=false
+
+proc NewCollision*(human, robot: Player, ball:Ball):Collision=
+    var sf=Collision(
+        human:human,
+        robot:robot,
+        ball:ball
+    )
+    return sf
+
+proc update*(sf:var Collision)=
+    sf.isColHuman = not (
+        sf.ball.x > sf.human.x + PaddleWidth or
+        sf.ball.x + 2 * BallRadius < sf.human.x or
+        sf.ball.y > sf.human.y + PaddleHeight or
+        sf.ball.y + 2 * BallRadius < sf.human.y
+    )
+    if sf.isColHuman:
+        sf.isColRobot=false
+        return
+    sf.isColRobot = not (
+        sf.ball.x > sf.robot.x + PaddleWidth or
+        sf.ball.x + 2 * BallRadius < sf.robot.x or
+        sf.ball.y > sf.robot.y + PaddleHeight or
+        sf.ball.y + 2 * BallRadius < sf.robot.y
+    )
+
+proc isCollisionHuman*(sf:Collision):bool=
+    return sf.isColHuman
+
+proc isCollisionRobot*(sf:Collision):bool=
+    return sf.isColRobot
+

+ 16 - 0
src/pong/cons.nim

@@ -0,0 +1,16 @@
+const
+
+    WindowWidth*  = 640
+    WindowHeight* = 480
+
+    PaddleWidth*  = 16
+    PaddleHeight* = 64
+    PaddleSpeed*  = 400.0
+
+    BallRadius* = 8
+
+    MaxBallComponentSpeed* = 1000'f32
+
+    TextWidth*  = 128
+    TextHeight* = 64
+

+ 28 - 0
src/pong/font.nim

@@ -0,0 +1,28 @@
+import sdl2
+import sdl2/ttf
+
+import ./cons
+
+type
+    Font* {.requiresInit.} = object
+        font:FontPtr
+        renderer:RendererPtr
+
+proc NewFont*(font:FontPtr, renderer:RendererPtr):Font=
+    let sf=Font(
+        font:font,
+        renderer:renderer,
+    )
+    return sf
+
+proc draw*(sf:Font, text:string, color:Color)=
+    let surface = ttf.renderTextSolid(sf.font, cstring text, color)
+    let texture = sf.renderer.createTextureFromSurface(surface)
+
+    var r = rect(
+        (WindowWidth - TextWidth) div 2,
+        0, TextWidth, TextHeight)
+    sf.renderer.copy(texture, nil, addr r)
+    surface.freeSurface()
+    texture.destroy()
+

+ 14 - 0
src/pong/input.nim

@@ -0,0 +1,14 @@
+import sdl2
+
+type
+    Input* {.pure.} = enum ## Направление движения
+        Up,
+        Down,
+        None
+
+func toInput*(key: Scancode): Input =
+  case key
+  of SDL_SCANCODE_UP: Input.Up
+  of SDL_SCANCODE_DOWN: Input.Down
+  else: Input.None
+

+ 88 - 0
src/pong/player.nim

@@ -0,0 +1,88 @@
+import sdl2
+
+import ../cons
+
+type
+    EPlayer {.pure.}= enum
+        Human,
+        Robot
+
+    Player* {.requiresInit.} = object
+        x: float32
+        y: float32
+        renderer: RendererPtr
+        case kind*: EPlayer
+        of Human:
+            name:string # у человека есть имя
+        of Robot:
+            id*: int # пример уникального поля для бота
+
+proc NewHuman*(x: float32=0, name:string="user", renderer:RendererPtr): Player =
+    assert(name!="", "NewHuman(): name is empty")
+    Player(
+        kind:Human,
+        x: x,
+        y: (WindowHeight + PaddleHeight) / 2,
+        renderer:renderer,
+        name:name,
+    )
+
+proc NewRobot*(x: float32=0, renderer:RendererPtr): Player =
+    Player(
+        kind:Robot,
+        x: x,
+        y: (WindowHeight + PaddleHeight) / 2,
+        renderer:renderer,
+        id:100,
+    )
+
+proc Stop*(sf:Player)=
+    discard
+
+proc updateRobot(sf:var Player, dy: float32) =
+    sf.y = sf.y + dy
+    sf.y = max(sf.y, 0)
+    sf.y = min(sf.y, WindowHeight - PaddleWidth)
+
+proc update*(sf:var Player, dt: float32)=
+    case sf.kind:
+    of Human:
+        discard
+    of Robot:
+        sf.updateRobot(dt)
+
+
+proc draw*(sf: Player) =
+    case sf.kind:
+    of Human:
+        sf.renderer.setDrawColor 255, 0, 0, 255 # red
+    of Robot:
+        sf.renderer.setDrawColor 0, 0, 255, 255 # blue
+    var r = rect(
+        cint(sf.x), cint(sf.y),
+        cint(PaddleWidth), cint(PaddleHeight)
+    )
+    sf.renderer.fillRect(r)
+
+proc `x`*(sf:Player):float32{.inline.}=
+    return sf.x
+
+proc `y`*(sf:Player):float32{.inline.}=
+    return sf.y
+
+proc `y=`*(sf:var Player, val:float32){.inline.}=
+    sf.y=val
+
+proc control(sf:var Player){.inline.}=
+    if sf.y < 0:
+        sf.y=0
+    elif sf.y + PaddleHeight > WindowHeight:
+        sf.y = WindowHeight - PaddleHeight
+
+proc Down*(sf:var Player, dt:float32){.inline.}=
+    sf.y -= PaddleSpeed * dt
+    sf.control()
+
+proc Up*(sf:var Player, dt:float32){.inline.}=
+    sf.y += PaddleSpeed * dt
+    sf.control()

+ 70 - 0
src/pong/pong.nim

@@ -0,0 +1,70 @@
+import sdl2
+
+import ./player
+import ./ball
+import ./cons
+import ./scores
+import ./font
+import ./collision
+
+
+type
+    Pong* = ref object
+        isRun: bool
+        human, robot: Player
+        ball: Ball
+        renderer:RendererPtr
+        scores: Scores
+        collision:Collision
+
+proc NewPong*(font:Font, renderer:RendererPtr): Pong =
+    var
+        human=NewHuman(cons.PaddleWidth, "user", renderer)
+        robot=NewRobot(cons.WindowWidth - 2 * cons.PaddleWidth, renderer)
+        scores= NewScores(font)
+        ball=NewBall(renderer, scores)
+    Pong(
+      isRun: true,
+      renderer:renderer,
+      human: human,
+      robot: robot,
+      ball: ball,
+      scores: scores,
+      collision:NewCollision(human, robot, ball)
+    )
+
+proc keyDown*(sf:var Pong, dt:float32)=
+    sf.human.Down(dt)
+
+proc keyUp*(sf:var Pong, dt:float32)=
+    sf.human.Up(dt)
+
+proc stopHuman*(sf: var Pong)=
+  sf.human.Stop()
+
+proc update*(sf:var  Pong, dt: float32) =
+    sf.collision.update()
+    sf.human.update(dt)
+    sf.robot.update(dt)
+    let
+        distRobot = (sf.ball.y + BallRadius) - (sf.robot.y + PaddleHeight / 2)
+        req = BallUpdateReq(
+            dTime:dt,
+            distRobot:distRobot,
+            isColHuman:sf.collision.isCollisionHuman,
+            isColRobot:sf.collision.isCollisionRobot,
+            humanX:sf.human.x,
+            robotX:sf.robot.x
+        )
+    sf.ball.update(req)
+
+
+proc draw*(sf:var Pong) =
+  sf.renderer.setDrawColor 0, 0, 0, 255 # black
+  sf.renderer.clear()
+  sf.human.draw()
+  sf.robot.draw()
+  sf.ball.draw()
+  sf.scores.draw()
+
+  sf.renderer.present()

+ 0 - 0
src/pong/pong_sdl.nim


+ 2 - 0
src/pong/render.nim

@@ -0,0 +1,2 @@
+import sdl2
+

+ 47 - 0
src/pong/scores.nim

@@ -0,0 +1,47 @@
+
+import sdl2
+
+import ./font
+
+type
+    Scores* {.requiresInit.}= object
+        robot:uint=0
+        human:uint=0
+        font:Font
+        color:Color
+
+proc NewScores*(font:Font):Scores=#FontPtr):Scores=
+    var sf=Scores(
+        font:font,
+        color:(r:255, g:255, b:255, a:0)
+    )
+    return sf
+
+proc draw*(sf:Scores)=
+    let
+        text = $sf.human & " vs " & $sf.robot
+        # surface = ttf.renderTextSolid(sf.font, cstring text, color)
+        # texture = renderer.createTextureFromSurface(surface)
+
+    # surface.freeSurface
+    # defer: texture.destroy
+
+    #   var r = rect(
+    #     (WindowWidth - TextWidth) div 2,
+    #     0,
+    #     TextWidth, TextHeight
+    #   )
+    #   renderer.copy texture, nil, addr r
+    sf.font.draw(text, sf.color)
+
+proc `robot`*(sf:Scores):uint=
+    return sf.robot
+
+proc robot_inc*(sf:var Scores)=
+    sf.robot += 1
+
+proc `human`*(sf:Scores):uint=
+    return sf.human
+
+proc human_inc*(sf:var Scores)=
+    sf.human += 1

+ 6 - 0
src/pong/tmpl.nim

@@ -0,0 +1,6 @@
+type SDLException = object of Defect
+
+template if_SDL_fail*(condition: typed, reason: string) =
+  if condition: raise SDLException.newException(
+    reason & ", SDL error " & $getError()
+  )